From 6002b6eac284c42a8483bed129311e51ce282a16 Mon Sep 17 00:00:00 2001 From: injectives <11927660+injectives@users.noreply.github.com> Date: Tue, 10 May 2022 13:19:03 +0100 Subject: [PATCH 1/4] Migrate RoutingExamplesIT to Testcontainers (#1215) --- driver/pom.xml | 10 + .../driver/stress/AbstractStressTestBase.java | 4 - .../driver/stress/CausalClusteringIT.java | 68 ---- .../stress/CausalClusteringStressIT.java | 7 +- .../stress/DumpLogsOnFailureWatcher.java | 50 --- .../driver/stress/SingleInstanceStressIT.java | 5 - .../org/neo4j/driver/util/cc/Cluster.java | 314 ------------------ .../neo4j/driver/util/cc/ClusterControl.java | 59 ---- .../neo4j/driver/util/cc/ClusterDrivers.java | 77 ----- .../driver/util/cc/ClusterExtension.java | 123 ------- .../neo4j/driver/util/cc/ClusterMember.java | 100 ------ .../driver/util/cc/ClusterMemberRole.java | 26 -- .../cc/ClusterMemberRoleDiscoveryFactory.java | 141 -------- .../util/cc/ClusterUnavailableException.java | 25 -- .../cc/LocalOrRemoteClusterExtension.java | 36 +- .../neo4j/driver/util/cc/SharedCluster.java | 137 -------- examples/pom.xml | 10 + .../neo4j/docs/driver/RoutingExamplesIT.java | 23 +- pom.xml | 16 +- 19 files changed, 67 insertions(+), 1164 deletions(-) delete mode 100644 driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java delete mode 100644 driver/src/test/java/org/neo4j/driver/stress/DumpLogsOnFailureWatcher.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/ClusterControl.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/ClusterDrivers.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/ClusterExtension.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRole.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/ClusterUnavailableException.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/SharedCluster.java diff --git a/driver/pom.xml b/driver/pom.xml index 5fad79247a..aefc5d9418 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -86,6 +86,16 @@ reactor-test test + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + neo4j + test + diff --git a/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java b/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java index fd138aea39..67bcd04b6f 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java @@ -59,7 +59,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.neo4j.driver.AuthToken; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Config; @@ -86,7 +85,6 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; -@ExtendWith(DumpLogsOnFailureWatcher.class) abstract class AbstractStressTestBase { private static final int THREAD_COUNT = Integer.getInteger("threadCount", 8); private static final int ASYNC_BATCH_SIZE = Integer.getInteger("asyncBatchSize", 10); @@ -183,8 +181,6 @@ private void runStressTest(Function>> threadLauncher) throws T verifyResults(context, resourcesInfo); } - abstract void dumpLogs(); - abstract URI databaseUri(); abstract AuthToken authToken(); diff --git a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java deleted file mode 100644 index f105eb8739..0000000000 --- a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.stress; - -import static org.neo4j.driver.Logging.none; -import static org.neo4j.driver.SessionConfig.builder; - -import java.net.URI; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Config; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Session; -import org.neo4j.driver.integration.NestedQueries; -import org.neo4j.driver.util.cc.ClusterExtension; - -public class CausalClusteringIT implements NestedQueries { - @RegisterExtension - static final ClusterExtension clusterRule = new ClusterExtension(); - - private Driver driver; - - @Override - public Session newSession(AccessMode mode) { - if (driver == null) { - driver = createDriver(clusterRule.getCluster().getRoutingUri()); - } - - return driver.session(builder().withDefaultAccessMode(mode).build()); - } - - @AfterEach - void tearDown() { - if (driver != null) { - driver.close(); - } - } - - private Driver createDriver(URI boltUri) { - return createDriver(boltUri, configWithoutLogging()); - } - - private Driver createDriver(URI boltUri, Config config) { - return GraphDatabase.driver(boltUri, clusterRule.getDefaultAuthToken(), config); - } - - private static Config configWithoutLogging() { - return Config.builder().withLogging(none()).build(); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringStressIT.java b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringStressIT.java index 87c04d11d6..e813413db1 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringStressIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringStressIT.java @@ -27,7 +27,9 @@ import org.neo4j.driver.Config; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.util.cc.LocalOrRemoteClusterExtension; +import org.testcontainers.junit.jupiter.Testcontainers; +@Testcontainers(disabledWithoutDocker = true) class CausalClusteringStressIT extends AbstractStressTestBase { @RegisterExtension static final LocalOrRemoteClusterExtension clusterRule = new LocalOrRemoteClusterExtension(); @@ -73,11 +75,6 @@ void printStats(Context context) { System.out.println("Bookmark failures: " + context.getBookmarkFailures()); } - @Override - void dumpLogs() { - clusterRule.dumpClusterLogs(); - } - @Override List> createTestSpecificBlockingCommands() { return Arrays.asList( diff --git a/driver/src/test/java/org/neo4j/driver/stress/DumpLogsOnFailureWatcher.java b/driver/src/test/java/org/neo4j/driver/stress/DumpLogsOnFailureWatcher.java deleted file mode 100644 index 03b0ff7e13..0000000000 --- a/driver/src/test/java/org/neo4j/driver/stress/DumpLogsOnFailureWatcher.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.stress; - -import java.util.Optional; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestWatcher; - -public class DumpLogsOnFailureWatcher implements TestWatcher { - @Override - public void testDisabled(ExtensionContext context, Optional reason) { - // do nothing - } - - @Override - public void testSuccessful(ExtensionContext context) { - // do nothing - } - - @Override - public void testAborted(ExtensionContext context, Throwable cause) { - // do nothing - } - - @Override - public void testFailed(ExtensionContext context, Throwable cause) { - if (context.getTestInstance().isPresent() - && context.getTestInstance().get() instanceof AbstractStressTestBase) { - AbstractStressTestBase clusterTest = - (AbstractStressTestBase) context.getTestInstance().get(); - clusterTest.dumpLogs(); - } - } -} diff --git a/driver/src/test/java/org/neo4j/driver/stress/SingleInstanceStressIT.java b/driver/src/test/java/org/neo4j/driver/stress/SingleInstanceStressIT.java index a6c0d33db7..81b77d862d 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/SingleInstanceStressIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/SingleInstanceStressIT.java @@ -116,9 +116,4 @@ List> createTestSpecificRxCommands() { } static class Context extends AbstractContext {} - - @Override - void dumpLogs() { - neo4j.dumpLogs(); - } } diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java b/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java deleted file mode 100644 index 8ec1b67574..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -import static java.util.Collections.emptySet; -import static java.util.Collections.unmodifiableSet; -import static org.neo4j.driver.util.TestUtil.sleep; - -import java.io.FileNotFoundException; -import java.net.URI; -import java.nio.file.Path; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import org.neo4j.driver.Bookmark; -import org.neo4j.driver.Driver; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.util.TestUtil; -import org.neo4j.driver.util.cc.ClusterMemberRoleDiscoveryFactory.ClusterMemberRoleDiscovery; - -public class Cluster implements AutoCloseable { - private static final String ADMIN_USER = "neo4j"; - private static final int STARTUP_TIMEOUT_SECONDS = 120; - private static final int ONLINE_MEMBERS_CHECK_SLEEP_MS = 500; - - private final Path path; - private final Set members; - private final Set offlineMembers; - private final ClusterDrivers clusterDrivers; - - public Cluster(Path path, String password) { - this(path, emptySet(), new ClusterDrivers(ADMIN_USER, password)); - } - - private Cluster(Path path, Set members, ClusterDrivers clusterDrivers) { - this.path = path; - this.members = members; - this.offlineMembers = new HashSet<>(); - this.clusterDrivers = clusterDrivers; - } - - Cluster withMembers(Set newMembers) throws ClusterUnavailableException { - waitForMembersToBeOnline(newMembers, clusterDrivers); - return new Cluster(path, newMembers, clusterDrivers); - } - - public URI getRoutingUri() { - return randomOf(cores()).getRoutingUri(); - } - - public Path getPath() { - return path; - } - - public void deleteData() { - // execute write query to remove all nodes and retrieve bookmark - Driver driverToLeader = clusterDrivers.getDriver(leader()); - Bookmark bookmark = TestUtil.cleanDb(driverToLeader); - if (bookmark == null) { - throw new IllegalStateException("Cleanup of the database did not produce a bookmark"); - } - - // ensure that every cluster member is up-to-date and contains no nodes - for (ClusterMember member : members) { - Driver driver = clusterDrivers.getDriver(member); - long nodeCount = TestUtil.countNodes(driver, bookmark); - if (nodeCount != 0) { - throw new IllegalStateException( - "Not all nodes have been deleted. " + nodeCount + " still there somehow"); - } - } - } - - public Set members() { - return unmodifiableSet(members); - } - - public ClusterMember leader() { - Set leaders = membersWithRole(ClusterMemberRole.LEADER); - if (leaders.size() != 1) { - throw new IllegalStateException("Single leader expected. " + leaders); - } - return leaders.iterator().next(); - } - - public ClusterMember anyFollower() { - return randomOf(followers()); - } - - public Set followers() { - return membersWithRole(ClusterMemberRole.FOLLOWER); - } - - public ClusterMember anyReadReplica() { - return randomOf(readReplicas()); - } - - public Set cores() { - Set readReplicas = membersWithRole(ClusterMemberRole.READ_REPLICA); - Set cores = new HashSet<>(members); - cores.removeAll(readReplicas); - return cores; - } - - public Set readReplicas() { - return membersWithRole(ClusterMemberRole.READ_REPLICA); - } - - public void start(ClusterMember member) { - startNoWait(member); - waitForMembersToBeOnline(); - } - - public Driver getDirectDriver(ClusterMember member) { - return clusterDrivers.getDriver(member); - } - - public void dumpClusterDebugLog() { - for (ClusterMember member : members) { - - System.out.println("Debug log for: " + member.getPath().toString()); - try { - member.dumpDebugLog(); - } catch (FileNotFoundException e) { - System.out.println( - "Unable to find debug log file for: " + member.getPath().toString()); - e.printStackTrace(); - } - } - } - - @Override - public void close() { - clusterDrivers.close(); - } - - @Override - public String toString() { - return "Cluster{" + "path=" + path + ", members=" + members + "}"; - } - - private void addOfflineMember(ClusterMember member) { - if (!offlineMembers.remove(member)) { - throw new IllegalArgumentException("Cluster member is not offline: " + member); - } - members.add(member); - } - - private void startNoWait(ClusterMember member) { - addOfflineMember(member); - SharedCluster.start(member); - } - - private Set membersWithRole(ClusterMemberRole role) { - Set membersWithRole = new HashSet<>(); - int retryCount = 0; - - while (membersWithRole.isEmpty() && retryCount < 10) { - Driver driver = driverToAnyCore(members, clusterDrivers); - final ClusterMemberRoleDiscovery discovery = clusterDrivers.getDiscovery(); - final Map clusterOverview = discovery.findClusterOverview(driver); - for (BoltServerAddress boltAddress : clusterOverview.keySet()) { - if (role == clusterOverview.get(boltAddress)) { - ClusterMember member = findByBoltAddress(boltAddress, members); - if (member == null) { - throw new IllegalStateException("Unknown cluster member: '" + boltAddress + "'\n" + this); - } - membersWithRole.add(member); - } - } - retryCount++; - - if (!membersWithRole.isEmpty()) { - break; - } else { - try { - // give some time for cluster to stabilise - Thread.sleep(2000); - } catch (InterruptedException ignored) { - } - } - } - - if (membersWithRole.isEmpty()) { - throw new IllegalStateException("No cluster members with role '" + role + " " + this); - } - - return membersWithRole; - } - - private void waitForMembersToBeOnline() { - try { - waitForMembersToBeOnline(members, clusterDrivers); - } catch (ClusterUnavailableException e) { - throw new RuntimeException(e); - } - } - - private static void waitForMembersToBeOnline(Set members, ClusterDrivers clusterDrivers) - throws ClusterUnavailableException { - if (members.isEmpty()) { - throw new IllegalArgumentException("No members to wait for"); - } - - Set expectedOnlineAddresses = extractBoltAddresses(members); - Set actualOnlineAddresses = emptySet(); - - long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(STARTUP_TIMEOUT_SECONDS); - Throwable error = null; - - while (!expectedOnlineAddresses.equals(actualOnlineAddresses)) { - sleep(ONLINE_MEMBERS_CHECK_SLEEP_MS); - assertDeadlineNotReached(deadline, expectedOnlineAddresses, actualOnlineAddresses, error); - - Driver driver = driverToAnyCore(members, clusterDrivers); - final ClusterMemberRoleDiscovery discovery = clusterDrivers.getDiscovery(); - try { - final Map clusterOverview = discovery.findClusterOverview(driver); - actualOnlineAddresses = clusterOverview.keySet(); - } catch (Throwable t) { - t.printStackTrace(); - - if (error == null) { - error = t; - } else { - error.addSuppressed(t); - } - } - } - } - - private static Driver driverToAnyCore(Set members, ClusterDrivers clusterDrivers) { - if (members.isEmpty()) { - throw new IllegalArgumentException("No members, can't create driver"); - } - - for (ClusterMember member : members) { - Driver driver = clusterDrivers.getDriver(member); - final ClusterMemberRoleDiscovery discovery = clusterDrivers.getDiscovery(); - if (discovery.isCoreMember(driver)) { - return driver; - } - } - - throw new IllegalStateException("No core members found among: " + members); - } - - private static void assertDeadlineNotReached( - long deadline, Set expectedAddresses, Set actualAddresses, Throwable error) - throws ClusterUnavailableException { - if (System.currentTimeMillis() > deadline) { - String baseMessage = "Cluster did not become available in " + STARTUP_TIMEOUT_SECONDS + " seconds.\n"; - String errorMessage = error == null ? "" : "There were errors checking cluster members.\n"; - String expectedAddressesMessage = "Expected online addresses: " + expectedAddresses + "\n"; - String actualAddressesMessage = "Actual last seen online addresses: " + actualAddresses + "\n"; - String message = baseMessage + errorMessage + expectedAddressesMessage + actualAddressesMessage; - - ClusterUnavailableException clusterUnavailable = new ClusterUnavailableException(message); - - if (error != null) { - clusterUnavailable.addSuppressed(error); - } - - throw clusterUnavailable; - } - } - - private static Set extractBoltAddresses(Set members) { - Set addresses = new HashSet<>(); - for (ClusterMember member : members) { - addresses.add(member.getBoltAddress()); - } - return addresses; - } - - private static ClusterMember findByBoltAddress(BoltServerAddress boltAddress, Set members) { - for (ClusterMember member : members) { - if (member.getBoltAddress().equals(boltAddress)) { - return member; - } - } - return null; - } - - private static ClusterMember randomOf(Set members) { - int randomIndex = ThreadLocalRandom.current().nextInt(members.size()); - int currentIndex = 0; - for (ClusterMember member : members) { - if (currentIndex == randomIndex) { - return member; - } - currentIndex++; - } - throw new AssertionError(); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterControl.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterControl.java deleted file mode 100644 index d297bb8401..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterControl.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -import static org.neo4j.driver.util.cc.CommandLineUtil.executeCommand; - -import java.nio.file.Path; - -final class ClusterControl { - private ClusterControl() {} - - static void installCluster(String neo4jVersion, int cores, int readReplicas, String password, int port, Path path) { - executeCommand( - "neoctrl-cluster", - "install", - "--cores", - String.valueOf(cores), - "--read-replicas", - String.valueOf(readReplicas), - "--password", - password, - "--initial-port", - String.valueOf(port), - neo4jVersion, - path.toString()); - } - - static String startCluster(Path path) { - return executeCommand("neoctrl-cluster", "start", path.toString()); - } - - static String startClusterMember(Path path) { - return executeCommand("neoctrl-start", path.toString()); - } - - static void stopCluster(Path path) { - executeCommand("neoctrl-cluster", "stop", path.toString()); - } - - static void killCluster(Path path) { - executeCommand("neoctrl-cluster", "stop", "--kill", path.toString()); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterDrivers.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterDrivers.java deleted file mode 100644 index 8e037566fa..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterDrivers.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.internal.util.ServerVersion.version; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Config; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.util.cc.ClusterMemberRoleDiscoveryFactory.ClusterMemberRoleDiscovery; - -public class ClusterDrivers implements AutoCloseable { - private final String user; - private final String password; - private final Map membersWithDrivers; - private ClusterMemberRoleDiscovery discovery; - - public ClusterDrivers(String user, String password) { - this.user = user; - this.password = password; - this.membersWithDrivers = new ConcurrentHashMap<>(); - } - - public Driver getDriver(ClusterMember member) { - final Driver driver = membersWithDrivers.computeIfAbsent(member, this::createDriver); - if (discovery == null) { - discovery = ClusterMemberRoleDiscoveryFactory.newInstance(version(driver)); - } - return driver; - } - - public ClusterMemberRoleDiscovery getDiscovery() { - return discovery; - } - - @Override - public void close() { - for (Driver driver : membersWithDrivers.values()) { - driver.close(); - } - } - - private Driver createDriver(ClusterMember member) { - return GraphDatabase.driver(member.getBoltUri(), AuthTokens.basic(user, password), driverConfig()); - } - - private static Config driverConfig() { - return Config.builder() - .withLogging(DEV_NULL_LOGGING) - .withoutEncryption() - .withMaxConnectionPoolSize(1) - .withConnectionLivenessCheckTimeout(0, TimeUnit.MILLISECONDS) - .withEventLoopThreads(1) - .build(); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterExtension.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterExtension.java deleted file mode 100644 index f2e90f576c..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterExtension.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.neo4j.driver.util.Neo4jRunner.PASSWORD; -import static org.neo4j.driver.util.Neo4jRunner.TARGET_DIR; -import static org.neo4j.driver.util.Neo4jRunner.USER; -import static org.neo4j.driver.util.cc.CommandLineUtil.boltKitAvailable; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.neo4j.driver.AuthToken; -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.util.Neo4jRunner; - -public class ClusterExtension implements BeforeAllCallback, AfterEachCallback, AfterAllCallback { - private static final Path CLUSTER_DIR = - Paths.get(TARGET_DIR, "test-cluster").toAbsolutePath(); - private static final int INITIAL_PORT = 20_000; - - public static final int CORE_COUNT = 3; - public static final int READ_REPLICA_COUNT = 2; - - public Cluster getCluster() { - return SharedCluster.get(); - } - - public AuthToken getDefaultAuthToken() { - return AuthTokens.basic(USER, PASSWORD); - } - - @Override - public void beforeAll(ExtensionContext context) throws Exception { - assumeTrue(boltKitAvailable(), "BoltKit cluster support unavailable"); - - stopSingleInstanceDatabase(); - - if (!SharedCluster.exists()) { - SharedCluster.install( - parseNeo4jVersion(), CORE_COUNT, READ_REPLICA_COUNT, PASSWORD, INITIAL_PORT, CLUSTER_DIR); - - try { - SharedCluster.start(); - } catch (Throwable startError) { - try { - SharedCluster.kill(); - } catch (Throwable killError) { - startError.addSuppressed(killError); - } finally { - SharedCluster.remove(); - } - throw startError; - } finally { - addShutdownHookToStopCluster(); - } - } - - getCluster().deleteData(); - } - - @Override - public void afterEach(ExtensionContext context) { - Cluster cluster = getCluster(); - cluster.deleteData(); - } - - @Override - public void afterAll(ExtensionContext context) { - if (SharedCluster.exists()) { - try { - SharedCluster.stop(); - } finally { - SharedCluster.remove(); - } - } - } - - private static String parseNeo4jVersion() { - String[] split = Neo4jRunner.NEOCTRL_ARGS.split("\\s+"); - return split[split.length - 1]; - } - - private static void stopSingleInstanceDatabase() throws IOException { - if (Neo4jRunner.globalRunnerExists()) { - Neo4jRunner.getOrCreateGlobalRunner().stopNeo4j(); - } - } - - private static void addShutdownHookToStopCluster() { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - if (SharedCluster.exists()) { - SharedCluster.kill(); - } - } catch (Throwable t) { - System.err.println("Cluster stopping shutdown hook failed"); - t.printStackTrace(); - } - })); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java deleted file mode 100644 index 66bf616c70..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -import static java.util.Objects.requireNonNull; - -import java.io.File; -import java.io.FileNotFoundException; -import java.net.InetAddress; -import java.net.URI; -import java.net.UnknownHostException; -import java.nio.file.Path; -import java.util.Objects; -import java.util.Scanner; -import org.neo4j.driver.internal.BoltServerAddress; - -public class ClusterMember { - public static final String SIMPLE_SCHEME = "bolt://"; - public static final String ROUTING_SCHEME = "neo4j://"; - - private final URI boltUri; - private final BoltServerAddress boltAddress; - private final Path path; - - public ClusterMember(URI boltUri, Path path) { - this.boltUri = requireNonNull(boltUri); - this.boltAddress = newBoltServerAddress(boltUri); - this.path = requireNonNull(path); - } - - public URI getBoltUri() { - return boltUri; - } - - public URI getRoutingUri() { - return URI.create(boltUri.toString().replace(SIMPLE_SCHEME, ROUTING_SCHEME)); - } - - public BoltServerAddress getBoltAddress() { - return boltAddress; - } - - public Path getPath() { - return path; - } - - public void dumpDebugLog() throws FileNotFoundException { - Scanner input = new Scanner(new File(path.toAbsolutePath().toString() + "/logs/debug.log")); - - while (input.hasNextLine()) { - System.out.println(input.nextLine()); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ClusterMember that = (ClusterMember) o; - return Objects.equals(boltAddress, that.boltAddress); - } - - @Override - public int hashCode() { - return Objects.hash(boltAddress); - } - - @Override - public String toString() { - return "ClusterMember{" + "boltUri=" + boltUri + ", boltAddress=" + boltAddress + ", path=" + path + '}'; - } - - private static BoltServerAddress newBoltServerAddress(URI uri) { - try { - return new BoltServerAddress(InetAddress.getByName(uri.getHost()).getHostAddress(), uri.getPort()); - } catch (UnknownHostException e) { - throw new RuntimeException(e); - } - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRole.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRole.java deleted file mode 100644 index 7c0a6f7074..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRole.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -public enum ClusterMemberRole { - LEADER, - FOLLOWER, - READ_REPLICA, - UNKNOWN -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java deleted file mode 100644 index f1e273de5b..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -import static org.neo4j.driver.SessionConfig.builder; -import static org.neo4j.driver.Values.parameters; -import static org.neo4j.driver.internal.util.Iterables.single; - -import java.net.InetAddress; -import java.net.URI; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Driver; -import org.neo4j.driver.Record; -import org.neo4j.driver.Result; -import org.neo4j.driver.Session; -import org.neo4j.driver.Values; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.util.ServerVersion; - -public class ClusterMemberRoleDiscoveryFactory { - public static ClusterMemberRoleDiscovery newInstance(ServerVersion version) { - if (version.greaterThanOrEqual(ServerVersion.v4_0_0)) { - return new SimpleClusterMemberRoleDiscovery(); - } else { - return new LegacyClusterMemberRoleDiscovery(); - } - } - - public interface ClusterMemberRoleDiscovery { - boolean isCoreMember(Driver driver); - - Map findClusterOverview(Driver driver); - } - - public static class LegacyClusterMemberRoleDiscovery implements ClusterMemberRoleDiscovery { - @Override - public boolean isCoreMember(Driver driver) { - try (Session session = driver.session( - builder().withDefaultAccessMode(AccessMode.READ).build())) { - Record record = single(session.run("CALL dbms.cluster.role()").list()); - ClusterMemberRole role = extractRole(record); - return role == ClusterMemberRole.LEADER || role == ClusterMemberRole.FOLLOWER; - } - } - - @Override - public Map findClusterOverview(Driver driver) { - try (Session session = driver.session( - builder().withDefaultAccessMode(AccessMode.WRITE).build())) { - Result result = session.run("CALL dbms.cluster.overview()"); - Map overview = new HashMap<>(); - for (Record record : result.list()) { - final BoltServerAddress address = extractBoltAddress(record); - final ClusterMemberRole role = extractRole(record); - overview.put(address, role); - } - return overview; - } - } - } - - public static class SimpleClusterMemberRoleDiscovery implements ClusterMemberRoleDiscovery { - private static final String DEFAULT_DATABASE = "neo4j"; - - @Override - public boolean isCoreMember(Driver driver) { - try (Session session = driver.session( - builder().withDefaultAccessMode(AccessMode.READ).build())) { - Record record = single( - session.run("CALL dbms.cluster.role($database)", parameters("database", DEFAULT_DATABASE)) - .list()); - ClusterMemberRole role = extractRole(record); - return role == ClusterMemberRole.LEADER || role == ClusterMemberRole.FOLLOWER; - } - } - - @Override - public Map findClusterOverview(Driver driver) { - try (Session session = driver.session( - builder().withDefaultAccessMode(AccessMode.READ).build())) { - Result result = session.run("CALL dbms.cluster.overview()"); - Map overview = new HashMap<>(); - for (Record record : result.list()) { - final BoltServerAddress address = extractBoltAddress(record); - final ClusterMemberRole role = extractRoleForDatabase(record, DEFAULT_DATABASE); - if (role != ClusterMemberRole.UNKNOWN) // the unknown ones has not fully come online - { - overview.put(address, role); - } - } - return overview; - } - } - } - - private static ClusterMemberRole extractRoleForDatabase(Record record, String database) { - final Map databases = record.get("databases").asMap(Values.ofString()); - final String roleString = databases.get(database); - return ClusterMemberRole.valueOf(roleString.toUpperCase()); - } - - private static BoltServerAddress extractBoltAddress(Record record) { - List addresses = record.get("addresses").asList(); - String boltUriString = (String) addresses.get(0); - URI boltUri = URI.create(boltUriString); - return newBoltServerAddress(boltUri); - } - - private static BoltServerAddress newBoltServerAddress(URI uri) { - try { - return new BoltServerAddress(InetAddress.getByName(uri.getHost()).getHostAddress(), uri.getPort()); - } catch (UnknownHostException e) { - throw new RuntimeException("Unable to resolve host to IP in URI: '" + uri + "'"); - } - } - - private static ClusterMemberRole extractRole(Record record) { - String roleString = record.get("role").asString(); - return ClusterMemberRole.valueOf(roleString.toUpperCase()); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterUnavailableException.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterUnavailableException.java deleted file mode 100644 index 64a53a2091..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterUnavailableException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -class ClusterUnavailableException extends Exception { - ClusterUnavailableException(String message) { - super(message); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java b/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java index 6c5825c24b..6c8623691a 100644 --- a/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java +++ b/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java @@ -19,6 +19,7 @@ package org.neo4j.driver.util.cc; import java.net.URI; +import java.util.Optional; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -29,12 +30,13 @@ import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; import org.neo4j.driver.util.TestUtil; +import org.testcontainers.containers.Neo4jContainer; public class LocalOrRemoteClusterExtension implements BeforeAllCallback, AfterEachCallback, AfterAllCallback { private static final String CLUSTER_URI_SYSTEM_PROPERTY_NAME = "externalClusterUri"; private static final String NEO4J_USER_PASSWORD_PROPERTY_NAME = "neo4jUserPassword"; - private ClusterExtension localClusterExtension; + private Neo4jContainer neo4jContainer; private URI clusterUri; public LocalOrRemoteClusterExtension() { @@ -49,44 +51,38 @@ public AuthToken getAuthToken() { if (remoteClusterExists()) { return AuthTokens.basic("neo4j", neo4jUserPasswordFromSystemProperty()); } - return localClusterExtension.getDefaultAuthToken(); + return AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword()); } @Override - public void beforeAll(ExtensionContext context) throws Exception { + public void beforeAll(ExtensionContext context) { if (remoteClusterExists()) { clusterUri = remoteClusterUriFromSystemProperty(); - deleteDataInRemoteCluster(); + cleanDb(); } else { - localClusterExtension = new ClusterExtension(); - localClusterExtension.beforeAll(context); - clusterUri = localClusterExtension.getCluster().getRoutingUri(); + String neo4JVersion = + Optional.ofNullable(System.getenv("NEO4J_VERSION")).orElse("4.4"); + neo4jContainer = new Neo4jContainer<>(String.format("neo4j:%s-enterprise", neo4JVersion)) + .withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes"); + neo4jContainer.start(); + + clusterUri = URI.create(neo4jContainer.getBoltUrl().replace("bolt://", "neo4j://")); } } @Override public void afterEach(ExtensionContext context) { - if (remoteClusterExists()) { - deleteDataInRemoteCluster(); - } else { - localClusterExtension.afterEach(context); - } + cleanDb(); } @Override public void afterAll(ExtensionContext context) { if (!remoteClusterExists()) { - localClusterExtension.afterAll(context); - } - } - - public void dumpClusterLogs() { - if (localClusterExtension != null) { - localClusterExtension.getCluster().dumpClusterDebugLog(); + neo4jContainer.stop(); } } - private void deleteDataInRemoteCluster() { + private void cleanDb() { Config.ConfigBuilder builder = Config.builder(); builder.withEventLoopThreads(1); diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/SharedCluster.java b/driver/src/test/java/org/neo4j/driver/util/cc/SharedCluster.java deleted file mode 100644 index b0cbeda022..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/SharedCluster.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -import static java.lang.System.lineSeparator; -import static org.neo4j.driver.util.Neo4jRunner.debug; - -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashSet; -import java.util.Set; - -final class SharedCluster { - private static Cluster clusterInstance; - - private SharedCluster() {} - - static Cluster get() { - assertClusterExists(); - return clusterInstance; - } - - static void remove() { - assertClusterExists(); - clusterInstance.close(); - clusterInstance = null; - } - - static boolean exists() { - return clusterInstance != null; - } - - static void install(String neo4jVersion, int cores, int readReplicas, String password, int port, Path path) { - assertClusterDoesNotExist(); - if (Files.isDirectory(path)) { - debug("Found and using cluster installed at `%s`.", path); - } else { - ClusterControl.installCluster(neo4jVersion, cores, readReplicas, password, port, path); - debug("Downloaded cluster at `%s`.", path); - } - clusterInstance = new Cluster(path, password); - } - - static void start() throws ClusterUnavailableException { - assertClusterExists(); - String output = ClusterControl.startCluster(clusterInstance.getPath()); - Set members = parseStartCommandOutput(output); - - try { - clusterInstance = clusterInstance.withMembers(members); - debug("Cluster started: %s.", members); - } catch (ClusterUnavailableException e) { - kill(); - throw e; - } - } - - static void start(ClusterMember member) { - assertClusterExists(); - ClusterControl.startClusterMember(member.getPath()); - debug("Cluster member at `%s` started.", member); - } - - static void stop() { - assertClusterExists(); - ClusterControl.stopCluster(clusterInstance.getPath()); - debug("Cluster at `%s` stopped.", clusterInstance.getPath()); - } - - static void kill() { - assertClusterExists(); - ClusterControl.killCluster(clusterInstance.getPath()); - debug("Cluster at `%s` killed.", clusterInstance.getPath()); - } - - private static Set parseStartCommandOutput(String output) { - Set result = new HashSet<>(); - - String[] lines = output.split(lineSeparator()); - for (int i = 0; i < lines.length; i++) { - String line = lines[i].trim(); - if (line.isEmpty()) { - // skip any empty lines - continue; - } - String[] clusterMemberSplit = line.split(" "); - if (clusterMemberSplit.length != 3) { - throw new IllegalArgumentException(String.format( - "Wrong start command output found at line [%s]. " - + "Expected to have 'http_uri bolt_uri path' on each nonempty line. " - + "Command output:%n`%s`", - i + 1, output)); - } - - URI boltUri = URI.create(clusterMemberSplit[1]); - Path path = Paths.get(clusterMemberSplit[2]); - - result.add(new ClusterMember(boltUri, path)); - } - - if (result.isEmpty()) { - throw new IllegalStateException("No cluster members"); - } - - return result; - } - - private static void assertClusterExists() { - if (clusterInstance == null) { - throw new IllegalStateException("Shared cluster does not exist"); - } - } - - private static void assertClusterDoesNotExist() { - if (clusterInstance != null) { - throw new IllegalStateException("Shared cluster already exists"); - } - } -} diff --git a/examples/pom.xml b/examples/pom.xml index 45a3de0282..0524e43b04 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -64,6 +64,16 @@ ch.qos.logback logback-classic + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + neo4j + test + diff --git a/examples/src/test/java/org/neo4j/docs/driver/RoutingExamplesIT.java b/examples/src/test/java/org/neo4j/docs/driver/RoutingExamplesIT.java index a02d216e4f..e015c3bdba 100644 --- a/examples/src/test/java/org/neo4j/docs/driver/RoutingExamplesIT.java +++ b/examples/src/test/java/org/neo4j/docs/driver/RoutingExamplesIT.java @@ -21,21 +21,32 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URI; +import java.util.Optional; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; +import org.neo4j.driver.AuthTokens; import org.neo4j.driver.net.ServerAddress; -import org.neo4j.driver.util.cc.ClusterExtension; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +@Testcontainers(disabledWithoutDocker = true) class RoutingExamplesIT { - @RegisterExtension - static final ClusterExtension neo4j = new ClusterExtension(); + private static final String NEO4J_VERSION = + Optional.ofNullable(System.getenv("NEO4J_VERSION")).orElse("4.4"); + + @Container + private static final Neo4jContainer NEO4J_CONTAINER = new Neo4jContainer<>( + String.format("neo4j:%s-enterprise", NEO4J_VERSION)) + .withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes") + .withAdminPassword(null); @Test void testShouldRunConfigCustomResolverExample() throws Exception { // Given - URI uri = neo4j.getCluster().getRoutingUri(); + URI boltUri = URI.create(NEO4J_CONTAINER.getBoltUrl()); + String neo4jUrl = String.format("neo4j://%s:%d", boltUri.getHost(), boltUri.getPort()); try (ConfigCustomResolverExample example = new ConfigCustomResolverExample( - "neo4j://x.example.com", neo4j.getDefaultAuthToken(), ServerAddress.of(uri.getHost(), uri.getPort()))) { + neo4jUrl, AuthTokens.none(), ServerAddress.of(boltUri.getHost(), boltUri.getPort()))) { // Then assertTrue(example.canConnect()); } diff --git a/pom.xml b/pom.xml index 3e798f1a51..6185914f67 100644 --- a/pom.xml +++ b/pom.xml @@ -47,10 +47,11 @@ 1.2.0 1.70 1.2.11 - 2.13.3 - 1.18.24 - 21.3.2.1 - 1.9.1 + 2.13.2 + 1.18.22 + 21.3.1 + 1.8.3 + 1.17.1 4.4.8 @@ -173,6 +174,13 @@ ${logback-classic.version} test + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + pom + import + From c405def0059d12b34cf3f2de1baa546669504091 Mon Sep 17 00:00:00 2001 From: injectives <11927660+injectives@users.noreply.github.com> Date: Wed, 18 May 2022 16:19:28 +0100 Subject: [PATCH 2/4] Migrate java-driver-rx-tck (#1224) * Migrate java-driver-rx-tck This update migrates verification tests from `java-driver-rx-tck` to this project. * Update surefire and failsafe to 3.0.0-M6 --- driver/pom.xml | 8 ++ ...RxResultRecordPublisherVerificationIT.java | 112 ++++++++++++++++++ pom.xml | 15 ++- 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 driver/src/test/java/org/neo4j/driver/tck/reactive/RxResultRecordPublisherVerificationIT.java diff --git a/driver/pom.xml b/driver/pom.xml index aefc5d9418..67078ba24b 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -69,6 +69,10 @@ org.junit.jupiter junit-jupiter + + org.junit.support + testng-engine + org.rauschig jarchivelib @@ -96,6 +100,10 @@ neo4j test + + org.reactivestreams + reactive-streams-tck + diff --git a/driver/src/test/java/org/neo4j/driver/tck/reactive/RxResultRecordPublisherVerificationIT.java b/driver/src/test/java/org/neo4j/driver/tck/reactive/RxResultRecordPublisherVerificationIT.java new file mode 100644 index 0000000000..f3c2f90281 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/tck/reactive/RxResultRecordPublisherVerificationIT.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.tck.reactive; + +import org.reactivestreams.Publisher; +import org.reactivestreams.tck.PublisherVerification; +import org.reactivestreams.tck.TestEnvironment; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testng.SkipException; +import org.testng.annotations.BeforeClass; + +import java.time.Duration; + +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Record; +import org.neo4j.driver.reactive.RxResult; +import org.neo4j.driver.reactive.RxSession; + +import static org.neo4j.driver.Values.parameters; + +@Testcontainers( disabledWithoutDocker = true ) +public class RxResultRecordPublisherVerificationIT extends PublisherVerification +{ + private static final Neo4jContainer NEO4J_CONTAINER = new Neo4jContainer<>( "neo4j:4.4" ) + .withAdminPassword( null ); + + private final static long MAX_NUMBER_OF_RECORDS = 30000; + + private static final Duration TIMEOUT = Duration.ofSeconds( 10 ); + private static final Duration TIMEOUT_FOR_NO_SIGNALS = Duration.ofSeconds( 1 ); + private static final Duration PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS = Duration.ofSeconds( 1 ); + + private final static String QUERY = "UNWIND RANGE(1, $numberOfRecords) AS n RETURN 'String Number' + n"; + + private Driver driver; + + public RxResultRecordPublisherVerificationIT() + { + super( new TestEnvironment( TIMEOUT.toMillis(), TIMEOUT_FOR_NO_SIGNALS.toMillis() ), + PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS.toMillis() ); + } + + @BeforeClass + public void beforeClass() + { + if ( !isDockerAvailable() ) + { + throw new SkipException( "Docker is unavailable" ); + } + NEO4J_CONTAINER.start(); + driver = GraphDatabase.driver( NEO4J_CONTAINER.getBoltUrl() ); + } + + public void afterClass() + { + NEO4J_CONTAINER.stop(); + } + + @Override + public long maxElementsFromPublisher() + { + return MAX_NUMBER_OF_RECORDS; + } + + @Override + public Publisher createPublisher( long elements ) + { + RxSession session = driver.rxSession(); + RxResult result = session.run( QUERY, parameters( "numberOfRecords", elements ) ); + return result.records(); + } + + @Override + public Publisher createFailedPublisher() + { + RxSession session = driver.rxSession(); + RxResult result = session.run( "INVALID" ); + return result.records(); + } + + boolean isDockerAvailable() + { + try + { + DockerClientFactory.instance().client(); + return true; + } + catch ( Throwable ex ) + { + return false; + } + } +} diff --git a/pom.xml b/pom.xml index 6185914f67..ff1e65af96 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 1C parallelizableIT - 2.22.1 + 3.0.0-M6 true @@ -44,6 +44,7 @@ 2.0.0.0 4.6.1 5.8.2 + 1.0.2 1.2.0 1.70 1.2.11 @@ -150,6 +151,12 @@ ${junit.version} test + + org.junit.support + testng-engine + ${testng-engine.version} + test + org.rauschig jarchivelib @@ -174,6 +181,12 @@ ${logback-classic.version} test + + org.reactivestreams + reactive-streams-tck + ${reactive-streams.version} + test + org.testcontainers testcontainers-bom From 14066c46ec859304c35a855e9513e2c05bcd520d Mon Sep 17 00:00:00 2001 From: injectives <11927660+injectives@users.noreply.github.com> Date: Mon, 4 Jul 2022 21:08:44 +0100 Subject: [PATCH 3/4] Migrate tests depending on Boltkit to use Docker (#1256) Boltkit has been deprecated and requires additional setup on the host to run tests. This update migrates tests depending on Boltkit to use Docker that is needed for Testkit testing as well. --- README.md | 17 +- driver/pom.xml | 4 + .../driver/integration/ConnectionPoolIT.java | 3 +- .../driver/integration/CredentialsIT.java | 62 +- .../driver/integration/DirectDriverIT.java | 16 - .../driver/integration/EncryptionIT.java | 10 +- .../org/neo4j/driver/integration/ErrorIT.java | 9 +- .../neo4j/driver/integration/LoadCSVIT.java | 22 +- .../driver/integration/ServerKilledIT.java | 13 +- .../neo4j/driver/integration/SessionIT.java | 4 +- .../driver/integration/SessionResetIT.java | 1460 ++++++++--------- .../integration/TrustCustomCertificateIT.java | 10 +- .../async/AsyncSessionServerRestartIT.java | 12 +- .../integration/async/AsyncTransactionIT.java | 2 +- .../integration/reactive/RxTransactionIT.java | 2 +- .../async/pool/NettyChannelPoolIT.java | 3 +- .../util/Neo4jWithFeatureCondition.java | 20 +- .../driver/util/CertificateExtension.java | 63 - .../neo4j/driver/util/CertificateUtil.java | 33 +- .../neo4j/driver/util/DatabaseExtension.java | 325 +++- .../org/neo4j/driver/util/Neo4jRunner.java | 317 ---- .../org/neo4j/driver/util/Neo4jSettings.java | 51 +- .../driver/util/ProcessEnvConfigurator.java | 64 - .../driver/util/cc/CommandLineException.java | 29 - .../neo4j/driver/util/cc/CommandLineUtil.java | 109 -- driver/src/test/resources/logback-test.xml | 14 + driver/src/test/resources/nginx.conf | 30 + examples/pom.xml | 4 + .../org/neo4j/docs/driver/ExamplesIT.java | 94 +- pom.xml | 2 +- testkit-tests/pom.xml | 2 +- 31 files changed, 1219 insertions(+), 1587 deletions(-) delete mode 100644 driver/src/test/java/org/neo4j/driver/util/CertificateExtension.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/Neo4jRunner.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/ProcessEnvConfigurator.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/CommandLineException.java delete mode 100644 driver/src/test/java/org/neo4j/driver/util/cc/CommandLineUtil.java create mode 100644 driver/src/test/resources/logback-test.xml create mode 100644 driver/src/test/resources/nginx.conf diff --git a/README.md b/README.md index f7c863f052..2debaa8573 100644 --- a/README.md +++ b/README.md @@ -96,19 +96,18 @@ To use the driver in a project, please use the released driver via Maven Central #### Running Tests and Creating a Package -Our test setup requires a bit of infrastructure to run. -We rely mostly on [`testkit`](https://github.com/neo4j-drivers/testkit). -Testkit is a tooling that is used to run integration tests for all of the drivers we provide. - -Some older tests still rely on [`boltkit`](https://github.com/neo4j-drivers/boltkit), the predecessor to Testkit. -If `boltkit` is not installed, then all tests that requires `boltkit` will be ignored and will not be executed. - -In case you want or can verify contributions with unit tests alone, use the following command to skip all integration and Testkit tests: - +The following command may be used to unit test and install artifacts without running integration tests: ``` mvn clean install -DskipITs -P skip-testkit ``` +Integration tests have the following prerequisites: +- Docker +- [`Testkit`](https://github.com/neo4j-drivers/testkit) + +Testkit that is a tooling that is used to run integration tests for all official Neo4j drivers. +It can be executed using Docker during Maven build and in such case does not require additional setup. See the instructions below for more details. + There are 2 ways of running Testkit tests: 1. Using the `testkit-tests` module of this project. 2. Manually cloning Testkit and running it directly. diff --git a/driver/pom.xml b/driver/pom.xml index 67078ba24b..3f40a10e9a 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -104,6 +104,10 @@ org.reactivestreams reactive-streams-tck + + ch.qos.logback + logback-classic + diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java index 02b849ae19..c9c7cf0d1f 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java @@ -80,7 +80,8 @@ void shouldRecoverFromDownedServer() throws Throwable { sessionGrabber.start(); // When - neo4j.forceRestartDb(); + neo4j.stopProxy(); + neo4j.startProxy(); // Then we accept a hump with failing sessions, but demand that failures stop as soon as the server is back up. sessionGrabber.assertSessionsAvailableWithin(120); diff --git a/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java b/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java index c5d2777878..720f40c75f 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java @@ -25,13 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.neo4j.driver.AuthTokens.basic; import static org.neo4j.driver.AuthTokens.custom; -import static org.neo4j.driver.Values.ofValue; -import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.util.Neo4jRunner.PASSWORD; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -44,11 +39,7 @@ import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.AuthenticationException; import org.neo4j.driver.exceptions.SecurityException; -import org.neo4j.driver.internal.security.InternalAuthToken; -import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; -import org.neo4j.driver.internal.util.Neo4jFeature; import org.neo4j.driver.util.DatabaseExtension; -import org.neo4j.driver.util.Neo4jSettings; import org.neo4j.driver.util.ParallelizableIT; @ParallelizableIT @@ -56,46 +47,10 @@ class CredentialsIT { @RegisterExtension static final DatabaseExtension neo4j = new DatabaseExtension(); - @Test - @DisabledOnNeo4jWith(Neo4jFeature.BOLT_V4) - // This feature is removed in 4.0 - void shouldBePossibleToChangePassword() throws Exception { - String newPassword = "secret"; - String tmpDataDir = Files.createTempDirectory(Paths.get("target"), "tmp") - .toAbsolutePath() - .toString() - .replace("\\", "/"); - - neo4j.restartDb(Neo4jSettings.TEST_SETTINGS.updateWith(Neo4jSettings.DATA_DIR, tmpDataDir)); - - AuthToken authToken = new InternalAuthToken(parameters( - "scheme", "basic", - "principal", "neo4j", - "credentials", "neo4j", - "new_credentials", newPassword) - .asMap(ofValue())); - - // change the password - try (Driver driver = GraphDatabase.driver(neo4j.uri(), authToken); - Session session = driver.session()) { - session.run("RETURN 1").consume(); - } - - // verify old password does not work - final Driver badDriver = GraphDatabase.driver(CredentialsIT.neo4j.uri(), basic("neo4j", PASSWORD)); - assertThrows(AuthenticationException.class, badDriver::verifyConnectivity); - - // verify new password works - try (Driver driver = GraphDatabase.driver(CredentialsIT.neo4j.uri(), AuthTokens.basic("neo4j", newPassword)); - Session session = driver.session()) { - session.run("RETURN 2").consume(); - } - } - @Test void basicCredentialsShouldWork() { // When & Then - try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("neo4j", PASSWORD)); + try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("neo4j", neo4j.adminPassword())); Session session = driver.session()) { Value single = session.run("RETURN 1").single().get(0); assertThat(single.asLong(), equalTo(1L)); @@ -105,7 +60,8 @@ void basicCredentialsShouldWork() { @Test void shouldGetHelpfulErrorOnInvalidCredentials() { SecurityException e = assertThrows(SecurityException.class, () -> { - try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("thisisnotthepassword", PASSWORD)); + try (Driver driver = + GraphDatabase.driver(neo4j.uri(), basic("thisisnotthepassword", neo4j.adminPassword())); Session session = driver.session()) { session.run("RETURN 1"); } @@ -116,7 +72,7 @@ void shouldGetHelpfulErrorOnInvalidCredentials() { @Test void shouldBeAbleToProvideRealmWithBasicAuth() { // When & Then - try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("neo4j", PASSWORD, "native")); + try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("neo4j", neo4j.adminPassword(), "native")); Session session = driver.session()) { Value single = session.run("CREATE () RETURN 1").single().get(0); assertThat(single.asLong(), equalTo(1L)); @@ -126,7 +82,8 @@ void shouldBeAbleToProvideRealmWithBasicAuth() { @Test void shouldBeAbleToConnectWithCustomToken() { // When & Then - try (Driver driver = GraphDatabase.driver(neo4j.uri(), custom("neo4j", PASSWORD, "native", "basic")); + try (Driver driver = + GraphDatabase.driver(neo4j.uri(), custom("neo4j", neo4j.adminPassword(), "native", "basic")); Session session = driver.session()) { Value single = session.run("CREATE () RETURN 1").single().get(0); assertThat(single.asLong(), equalTo(1L)); @@ -138,7 +95,8 @@ void shouldBeAbleToConnectWithCustomTokenWithAdditionalParameters() { Map params = singletonMap("secret", 16); // When & Then - try (Driver driver = GraphDatabase.driver(neo4j.uri(), custom("neo4j", PASSWORD, "native", "basic", params)); + try (Driver driver = GraphDatabase.driver( + neo4j.uri(), custom("neo4j", neo4j.adminPassword(), "native", "basic", params)); Session session = driver.session()) { Value single = session.run("CREATE () RETURN 1").single().get(0); assertThat(single.asLong(), equalTo(1L)); @@ -147,12 +105,12 @@ void shouldBeAbleToConnectWithCustomTokenWithAdditionalParameters() { @Test void directDriverShouldFailEarlyOnWrongCredentials() { - testDriverFailureOnWrongCredentials("bolt://localhost:" + neo4j.boltPort()); + testDriverFailureOnWrongCredentials(neo4j.uri().toString()); } @Test void routingDriverShouldFailEarlyOnWrongCredentials() { - testDriverFailureOnWrongCredentials("neo4j://localhost:" + neo4j.boltPort()); + testDriverFailureOnWrongCredentials(neo4j.uri().toString()); } private void testDriverFailureOnWrongCredentials(String uri) { diff --git a/driver/src/test/java/org/neo4j/driver/integration/DirectDriverIT.java b/driver/src/test/java/org/neo4j/driver/integration/DirectDriverIT.java index 442ab39b56..ecd2d3c2d0 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/DirectDriverIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/DirectDriverIT.java @@ -25,14 +25,11 @@ import static org.neo4j.driver.internal.util.Matchers.directDriverWithAddress; import java.net.URI; -import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Result; -import org.neo4j.driver.Session; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.util.DatabaseExtension; import org.neo4j.driver.util.ParallelizableIT; @@ -87,17 +84,4 @@ void shouldRegisterSingleServer() { // Then assertThat(driver, is(directDriverWithAddress(address))); } - - @Test - void shouldConnectIPv6Uri() { - // Given - try (Driver driver = GraphDatabase.driver("bolt://[::1]:" + neo4j.boltPort(), neo4j.authToken()); - Session session = driver.session()) { - // When - Result result = session.run("RETURN 1"); - - // Then - assertThat(result.single().get(0).asInt(), CoreMatchers.equalTo(1)); - } - } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/EncryptionIT.java b/driver/src/test/java/org/neo4j/driver/integration/EncryptionIT.java index 692fd1245c..2337b01aa9 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/EncryptionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/EncryptionIT.java @@ -25,6 +25,8 @@ import static org.neo4j.driver.Config.TrustStrategy.trustAllCertificates; import java.net.URI; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.neo4j.driver.Config; @@ -81,7 +83,9 @@ void shouldOperateWithoutEncryptionWhenItIsAlsoDisabledInTheDatabase() { } private void testMatchingEncryption(BoltTlsLevel tlsLevel, boolean driverEncrypted, String scheme) { - neo4j.restartDb(Neo4jSettings.TEST_SETTINGS.updateWith(Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString())); + Map tlsConfig = new HashMap<>(); + tlsConfig.put(Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString()); + neo4j.deleteAndStartNeo4j(tlsConfig); Config config; if (scheme.equals(Scheme.BOLT_LOW_TRUST_URI_SCHEME)) { @@ -107,7 +111,9 @@ private void testMatchingEncryption(BoltTlsLevel tlsLevel, boolean driverEncrypt } private void testMismatchingEncryption(BoltTlsLevel tlsLevel, boolean driverEncrypted) { - neo4j.restartDb(Neo4jSettings.TEST_SETTINGS.updateWith(Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString())); + Map tlsConfig = new HashMap<>(); + tlsConfig.put(Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString()); + neo4j.deleteAndStartNeo4j(tlsConfig); Config config = newConfig(driverEncrypted); ServiceUnavailableException e = assertThrows( diff --git a/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java b/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java index c03e749c9b..607c51e3ba 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java @@ -171,13 +171,12 @@ void shouldHandleFailureAtRunTime() { } @Test - void shouldGetHelpfulErrorWhenTryingToConnectToHttpPort() throws Throwable { - // the http server needs some time to start up - Thread.sleep(2000); - + void shouldGetHelpfulErrorWhenTryingToConnectToHttpPort() { Config config = Config.builder().withoutEncryption().build(); - final Driver driver = GraphDatabase.driver("bolt://localhost:" + session.httpPort(), config); + URI boltUri = session.uri(); + URI uri = URI.create(String.format("%s://%s:%d", boltUri.getScheme(), boltUri.getHost(), session.httpPort())); + final Driver driver = GraphDatabase.driver(uri, config); ClientException e = assertThrows(ClientException.class, driver::verifyConnectivity); assertEquals( "Server responded HTTP. Make sure you are not trying to connect to the http endpoint " diff --git a/driver/src/test/java/org/neo4j/driver/integration/LoadCSVIT.java b/driver/src/test/java/org/neo4j/driver/integration/LoadCSVIT.java index db41d87b10..44abb08f48 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/LoadCSVIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/LoadCSVIT.java @@ -31,14 +31,12 @@ import org.neo4j.driver.Result; import org.neo4j.driver.Session; import org.neo4j.driver.util.DatabaseExtension; -import org.neo4j.driver.util.Neo4jSettings; import org.neo4j.driver.util.ParallelizableIT; @ParallelizableIT class LoadCSVIT { @RegisterExtension - static final DatabaseExtension neo4j = - new DatabaseExtension(Neo4jSettings.TEST_SETTINGS.without(Neo4jSettings.IMPORT_DIR)); + static final DatabaseExtension neo4j = new DatabaseExtension(); @Test void shouldLoadCSV() throws Throwable { @@ -47,13 +45,21 @@ void shouldLoadCSV() throws Throwable { String csvFileUrl = createLocalIrisData(session); // When - Result result = session.run( - "USING PERIODIC COMMIT 40\n" + "LOAD CSV WITH HEADERS FROM $csvFileUrl AS l\n" + String query = neo4j.isNeo4j44OrEarlier() + ? "USING PERIODIC COMMIT 40\n" + "LOAD CSV WITH HEADERS FROM $csvFileUrl AS l\n" + "MATCH (c:Class {name: l.class_name})\n" + "CREATE (s:Sample {sepal_length: l.sepal_length, sepal_width: l.sepal_width, petal_length: l.petal_length, petal_width: l.petal_width})\n" + "CREATE (c)<-[:HAS_CLASS]-(s) " - + "RETURN count(*) AS c", - parameters("csvFileUrl", csvFileUrl)); + + "RETURN count(*) AS c" + : "LOAD CSV WITH HEADERS FROM $csvFileUrl AS l\n" + "CALL {\n" + + "WITH l\n" + + "MATCH (c:Class {name: l.class_name})\n" + + "CREATE (s:Sample {sepal_length: l.sepal_length, sepal_width: l.sepal_width, petal_length: l.petal_length, petal_width: l.petal_width})\n" + + "CREATE (c)<-[:HAS_CLASS]-(s)" + + "} IN TRANSACTIONS\n" + + "RETURN count(*) AS c"; + + Result result = session.run(query, parameters("csvFileUrl", csvFileUrl)); // Then assertThat(result.next().get("c").asInt(), equalTo(150)); @@ -66,7 +72,7 @@ private String createLocalIrisData(Session session) throws IOException { session.run("CREATE (c:Class {name: $className}) RETURN c", parameters("className", className)); } - return neo4j.putTmpFile("iris", ".csv", IRIS_DATA).toExternalForm(); + return neo4j.addImportFile("iris", ".csv", IRIS_DATA); } private static String[] IRIS_CLASS_NAMES = new String[] {"Iris-setosa", "Iris-versicolor", "Iris-virginica"}; diff --git a/driver/src/test/java/org/neo4j/driver/integration/ServerKilledIT.java b/driver/src/test/java/org/neo4j/driver/integration/ServerKilledIT.java index b57b91a690..cc15f8df61 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ServerKilledIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ServerKilledIT.java @@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.neo4j.driver.Config.TrustStrategy.trustCustomCertificateSignedBy; -import static org.neo4j.driver.util.Neo4jRunner.DEFAULT_AUTH_TOKEN; import java.util.List; import java.util.concurrent.TimeUnit; @@ -69,11 +68,12 @@ private static Stream data() { @MethodSource("data") void shouldRecoverFromServerRestart(String name, Config.ConfigBuilder configBuilder) { // Given config with sessionLivenessCheckTimeout not set, i.e. turned off - try (Driver driver = GraphDatabase.driver(neo4j.uri(), DEFAULT_AUTH_TOKEN, configBuilder.build())) { + try (Driver driver = GraphDatabase.driver(neo4j.uri(), neo4j.authToken(), configBuilder.build())) { acquireAndReleaseConnections(4, driver); // When - neo4j.forceRestartDb(); + neo4j.stopProxy(); + neo4j.startProxy(); // Then we should be able to start using sessions again, at most O(numSessions) session calls later int toleratedFailures = 4; @@ -102,8 +102,9 @@ void shouldDropBrokenOldSessions(String name, Config.ConfigBuilder configBuilder try (Driver driver = createDriver(clock, configBuilder.build())) { acquireAndReleaseConnections(5, driver); - // restart database to invalidate all idle connections in the pool - neo4j.forceRestartDb(); + // restart database access to invalidate all idle connections in the pool + neo4j.stopProxy(); + neo4j.startProxy(); // move clock forward more than configured liveness check timeout clock.progress(TimeUnit.MINUTES.toMillis(livenessCheckTimeoutMinutes + 1)); @@ -131,6 +132,6 @@ private Driver createDriver(Clock clock, Config config) { RoutingSettings routingSettings = RoutingSettings.DEFAULT; RetrySettings retrySettings = RetrySettings.DEFAULT; return factory.newInstance( - neo4j.uri(), DEFAULT_AUTH_TOKEN, routingSettings, retrySettings, config, SecurityPlanImpl.insecure()); + neo4j.uri(), neo4j.authToken(), routingSettings, retrySettings, config, SecurityPlanImpl.insecure()); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java index 709b0407d0..c894f655e5 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java @@ -50,7 +50,6 @@ import static org.neo4j.driver.internal.util.Matchers.connectionAcquisitionTimeoutError; import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V4; import static org.neo4j.driver.util.DaemonThreadFactory.daemon; -import static org.neo4j.driver.util.Neo4jRunner.DEFAULT_AUTH_TOKEN; import java.util.HashSet; import java.util.List; @@ -1246,10 +1245,9 @@ private Driver newDriverWithoutRetries() { private Driver newDriverWithFixedRetries(int maxRetriesCount) { DriverFactory driverFactory = new DriverFactoryWithFixedRetryLogic(maxRetriesCount); - AuthToken auth = DEFAULT_AUTH_TOKEN; return driverFactory.newInstance( neo4j.uri(), - auth, + neo4j.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, noLoggingConfig(), diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java index 14e4f63e85..df0727de5a 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java @@ -16,733 +16,733 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.integration; - -import static java.util.Collections.newSetFromMap; -import static java.util.concurrent.CompletableFuture.runAsync; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.SECONDS; -import static java.util.stream.IntStream.range; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.lessThanOrEqualTo; -import static org.hamcrest.junit.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.neo4j.driver.Values.parameters; -import static org.neo4j.driver.util.DaemonThreadFactory.daemon; -import static org.neo4j.driver.util.Neo4jRunner.HOME_DIR; -import static org.neo4j.driver.util.Neo4jSettings.IMPORT_DIR; -import static org.neo4j.driver.util.Neo4jSettings.TEST_SETTINGS; -import static org.neo4j.driver.util.TestUtil.activeQueryCount; -import static org.neo4j.driver.util.TestUtil.activeQueryNames; -import static org.neo4j.driver.util.TestUtil.await; -import static org.neo4j.driver.util.TestUtil.awaitAllFutures; -import static org.neo4j.driver.util.TestUtil.awaitCondition; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.URI; -import java.nio.channels.ClosedChannelException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.neo4j.driver.Driver; -import org.neo4j.driver.QueryRunner; -import org.neo4j.driver.Result; -import org.neo4j.driver.Session; -import org.neo4j.driver.Transaction; -import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.exceptions.Neo4jException; -import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.exceptions.TransientException; -import org.neo4j.driver.util.DatabaseExtension; -import org.neo4j.driver.util.ParallelizableIT; - -@SuppressWarnings("deprecation") -@ParallelizableIT -class SessionResetIT { - private static final int CSV_FILE_SIZE = 10_000; - private static final int LOAD_CSV_BATCH_SIZE = 10; - - private static final String SHORT_QUERY_1 = "CREATE (n:Node {name: 'foo', occupation: 'bar'})"; - private static final String SHORT_QUERY_2 = "MATCH (n:Node {name: 'foo'}) RETURN count(n)"; - private static final String LONG_QUERY = "UNWIND range(0, 10000000) AS i CREATE (n:Node {idx: i}) DELETE n"; - private static final String LONG_PERIODIC_COMMIT_QUERY_TEMPLATE = "USING PERIODIC COMMIT 1 " - + "LOAD CSV FROM '%s' AS line " - + "UNWIND range(1, " - + LOAD_CSV_BATCH_SIZE + ") AS index " + "CREATE (n:Node {id: index, name: line[0], occupation: line[1]})"; - - private static final int STRESS_TEST_THREAD_COUNT = Runtime.getRuntime().availableProcessors() * 2; - private static final long STRESS_TEST_DURATION_MS = SECONDS.toMillis(5); - private static final String[] STRESS_TEST_QUERIES = {SHORT_QUERY_1, SHORT_QUERY_2, LONG_QUERY}; - - @RegisterExtension - static final DatabaseExtension neo4j = new DatabaseExtension(); - - private ExecutorService executor; - - @BeforeEach - void setUp() { - executor = Executors.newCachedThreadPool(daemon(getClass().getSimpleName() + "-thread")); - } - - @AfterEach - void tearDown() { - if (executor != null) { - executor.shutdownNow(); - } - } - - @Test - void shouldTerminateAutoCommitQuery() { - testQueryTermination(LONG_QUERY, true); - } - - @Test - void shouldTerminateQueryInUnmanagedTransaction() { - testQueryTermination(LONG_QUERY, false); - } - - /** - * It is currently unsafe to terminate periodic commit query because it'll then be half-committed. - * So the driver give no guarantee when the periodic commit could be terminated. - * For a user who want to terminate a periodic commit, he or she should use kill query by id. - */ - @Test - void shouldTerminatePeriodicCommitQueryRandomly() { - Future queryResult = runQueryInDifferentThreadAndResetSession(longPeriodicCommitQuery(), true); - - ExecutionException e = assertThrows(ExecutionException.class, () -> queryResult.get(1, MINUTES)); - assertThat(e.getCause(), instanceOf(Neo4jException.class)); - - awaitNoActiveQueries(); - - assertThat(countNodes(), lessThanOrEqualTo(((long) CSV_FILE_SIZE) * LOAD_CSV_BATCH_SIZE)); - } - - @Test - void shouldTerminateAutoCommitQueriesRandomly() throws Exception { - testRandomQueryTermination(true); - } - - @Test - void shouldTerminateQueriesInUnmanagedTransactionsRandomly() throws Exception { - testRandomQueryTermination(false); - } - - @Test - void shouldRejectNewTransactionWhenOpenTransactionExistsAndShouldFailRunResultOnSessionReset() throws Throwable { - neo4j.ensureProcedures("longRunningStatement.jar"); - - try (Session session = neo4j.driver().session()) { - Transaction tx1 = session.beginTransaction(); - - CompletableFuture txRunFuture = CompletableFuture.runAsync( - () -> tx1.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); - - awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); - session.reset(); - - ClientException e1 = assertThrows(ClientException.class, session::beginTransaction); - assertThat( - e1.getMessage(), - containsString("You cannot begin a transaction on a session with an open transaction")); - - ClientException e2 = assertThrows(ClientException.class, () -> tx1.run("RETURN 1")); - assertThat(e2.getMessage(), containsString("Cannot run more queries in this transaction")); - - // Make sure failure from the terminated long running query is propagated - Neo4jException e3 = assertThrows(Neo4jException.class, () -> await(txRunFuture)); - assertThat(e3.getMessage(), containsString("The transaction has been terminated")); - } - } - - @Test - void shouldSuccessfullyCloseAfterSessionReset() throws Throwable { - neo4j.ensureProcedures("longRunningStatement.jar"); - - try (Session session = neo4j.driver().session()) { - CompletableFuture.runAsync( - () -> session.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); - - awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); - session.reset(); - } - } - - @Test - void shouldBeAbleToBeginNewTransactionAfterFirstTransactionInterruptedBySessionResetIsClosed() throws Throwable { - neo4j.ensureProcedures("longRunningStatement.jar"); - - try (Session session = neo4j.driver().session()) { - Transaction tx1 = session.beginTransaction(); - - CompletableFuture txRunFuture = runAsync( - () -> tx1.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); - - awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); - session.reset(); - - Neo4jException e = assertThrows(Neo4jException.class, () -> await(txRunFuture)); - assertThat(e.getMessage(), containsString("The transaction has been terminated")); - tx1.close(); - - try (Transaction tx2 = session.beginTransaction()) { - tx2.run("CREATE (n:FirstNode)"); - tx2.commit(); - } - - Result result = session.run("MATCH (n) RETURN count(n)"); - long nodes = result.single().get("count(n)").asLong(); - MatcherAssert.assertThat(nodes, equalTo(1L)); - } - } - - @Test - void shouldKillLongRunningQuery() throws Throwable { - neo4j.ensureProcedures("longRunningStatement.jar"); - - final int executionTimeout = 10; // 10s - final int killTimeout = 1; // 1s - final AtomicLong startTime = new AtomicLong(-1); - long endTime; - - try (Session session = neo4j.driver().session()) { - CompletableFuture sessionRunFuture = CompletableFuture.runAsync(() -> { - // When - startTime.set(System.currentTimeMillis()); - session.run( - "CALL test.driver.longRunningStatement({seconds})", parameters("seconds", executionTimeout)); - }); - - resetSessionAfterTimeout(session, killTimeout); - - assertThrows(Neo4jException.class, () -> await(sessionRunFuture)); - } - - endTime = System.currentTimeMillis(); - assertTrue(startTime.get() > 0); - assertTrue(endTime - startTime.get() > killTimeout * 1000); // get reset by session.reset - assertTrue(endTime - startTime.get() < executionTimeout * 1000 / 2); // finished before execution finished - } - - @Test - void shouldKillLongStreamingResult() throws Throwable { - neo4j.ensureProcedures("longRunningStatement.jar"); - // Given - final int executionTimeout = 10; // 10s - final int killTimeout = 1; // 1s - final AtomicInteger recordCount = new AtomicInteger(); - final AtomicLong startTime = new AtomicLong(-1); - long endTime; - - Neo4jException e = assertThrows(Neo4jException.class, () -> { - try (Session session = neo4j.driver().session()) { - Result result = session.run( - "CALL test.driver.longStreamingResult({seconds})", parameters("seconds", executionTimeout)); - - resetSessionAfterTimeout(session, killTimeout); - - // When - startTime.set(System.currentTimeMillis()); - while (result.hasNext()) { - result.next(); - recordCount.incrementAndGet(); - } - } - }); - - endTime = System.currentTimeMillis(); - assertThat(e.getMessage(), containsString("The transaction has been terminated")); - assertThat(recordCount.get(), greaterThan(1)); - - assertTrue(startTime.get() > 0); - assertTrue(endTime - startTime.get() > killTimeout * 1000); // get reset by session.reset - assertTrue(endTime - startTime.get() < executionTimeout * 1000 / 2); // finished before execution finished - } - - private void resetSessionAfterTimeout(Session session, int timeout) { - executor.submit(() -> { - try { - Thread.sleep(timeout * 1000); // let the query execute for timeout seconds - } catch (InterruptedException ignore) { - } finally { - session.reset(); // reset the session after timeout - } - }); - } - - @Test - void shouldAllowMoreQueriesAfterSessionReset() { - // Given - try (Session session = neo4j.driver().session()) { - - session.run("RETURN 1").consume(); - - // When reset the state of this session - session.reset(); - - // Then can run successfully more queries without any error - session.run("RETURN 2").consume(); - } - } - - @Test - void shouldAllowMoreTxAfterSessionReset() { - // Given - try (Session session = neo4j.driver().session()) { - try (Transaction tx = session.beginTransaction()) { - tx.run("RETURN 1"); - tx.commit(); - } - - // When reset the state of this session - session.reset(); - - // Then can run more Tx - try (Transaction tx = session.beginTransaction()) { - tx.run("RETURN 2"); - tx.commit(); - } - } - } - - @Test - void shouldMarkTxAsFailedAndDisallowRunAfterSessionReset() { - // Given - try (Session session = neo4j.driver().session()) { - Transaction tx = session.beginTransaction(); - // When reset the state of this session - session.reset(); - - // Then - Exception e = assertThrows(Exception.class, () -> { - tx.run("RETURN 1"); - tx.commit(); - }); - assertThat(e.getMessage(), startsWith("Cannot run more queries in this transaction")); - } - } - - @Test - void shouldAllowMoreTxAfterSessionResetInTx() { - // Given - try (Session session = neo4j.driver().session()) { - try (Transaction ignore = session.beginTransaction()) { - // When reset the state of this session - session.reset(); - } - - // Then can run more Tx - try (Transaction tx = session.beginTransaction()) { - tx.run("RETURN 2"); - tx.commit(); - } - } - } - - @Test - void resetShouldStopQueryWaitingForALock() throws Exception { - testResetOfQueryWaitingForLock(new NodeIdUpdater() { - @Override - void performUpdate( - Driver driver, - int nodeId, - int newNodeId, - AtomicReference usedSessionRef, - CountDownLatch latchToWait) - throws Exception { - try (Session session = driver.session()) { - usedSessionRef.set(session); - latchToWait.await(); - Result result = updateNodeId(session, nodeId, newNodeId); - result.consume(); - } - } - }); - } - - @Test - void resetShouldStopTransactionWaitingForALock() throws Exception { - testResetOfQueryWaitingForLock(new NodeIdUpdater() { - @Override - public void performUpdate( - Driver driver, - int nodeId, - int newNodeId, - AtomicReference usedSessionRef, - CountDownLatch latchToWait) - throws Exception { - try (Session session = neo4j.driver().session(); - Transaction tx = session.beginTransaction()) { - usedSessionRef.set(session); - latchToWait.await(); - Result result = updateNodeId(tx, nodeId, newNodeId); - result.consume(); - } - } - }); - } - - @Test - void resetShouldStopWriteTransactionWaitingForALock() throws Exception { - AtomicInteger invocationsOfWork = new AtomicInteger(); - - testResetOfQueryWaitingForLock(new NodeIdUpdater() { - @Override - public void performUpdate( - Driver driver, - int nodeId, - int newNodeId, - AtomicReference usedSessionRef, - CountDownLatch latchToWait) - throws Exception { - try (Session session = driver.session()) { - usedSessionRef.set(session); - latchToWait.await(); - - session.writeTransaction(tx -> { - invocationsOfWork.incrementAndGet(); - Result result = updateNodeId(tx, nodeId, newNodeId); - result.consume(); - return null; - }); - } - } - }); - - assertEquals(1, invocationsOfWork.get()); - } - - @Test - void shouldBeAbleToRunMoreQueriesAfterResetOnNoErrorState() { - try (Session session = neo4j.driver().session()) { - // Given - session.reset(); - - // When - Transaction tx = session.beginTransaction(); - tx.run("CREATE (n:FirstNode)"); - tx.commit(); - - // Then the outcome of both queries should be visible - Result result = session.run("MATCH (n) RETURN count(n)"); - long nodes = result.single().get("count(n)").asLong(); - assertThat(nodes, equalTo(1L)); - } - } - - @Test - void shouldHandleResetBeforeRun() { - try (Session session = neo4j.driver().session(); - Transaction tx = session.beginTransaction()) { - session.reset(); - - ClientException e = assertThrows(ClientException.class, () -> tx.run("CREATE (n:FirstNode)")); - assertThat(e.getMessage(), containsString("Cannot run more queries in this transaction")); - } - } - - @Test - void shouldHandleResetFromMultipleThreads() throws Throwable { - Session session = neo4j.driver().session(); - - CountDownLatch beforeCommit = new CountDownLatch(1); - CountDownLatch afterReset = new CountDownLatch(1); - - Future txFuture = executor.submit(() -> { - Transaction tx1 = session.beginTransaction(); - tx1.run("CREATE (n:FirstNode)"); - beforeCommit.countDown(); - afterReset.await(); - - // session has been reset, it should not be possible to commit the transaction - try { - tx1.commit(); - } catch (Neo4jException ignore) { - } - - try (Transaction tx2 = session.beginTransaction()) { - tx2.run("CREATE (n:SecondNode)"); - tx2.commit(); - } - - return null; - }); - - Future resetFuture = executor.submit(() -> { - beforeCommit.await(); - session.reset(); - afterReset.countDown(); - return null; - }); - - executor.shutdown(); - executor.awaitTermination(20, SECONDS); - - txFuture.get(20, SECONDS); - resetFuture.get(20, SECONDS); - - assertEquals(0, countNodes("FirstNode")); - assertEquals(1, countNodes("SecondNode")); - } - - private void testResetOfQueryWaitingForLock(NodeIdUpdater nodeIdUpdater) throws Exception { - int nodeId = 42; - int newNodeId1 = 4242; - int newNodeId2 = 424242; - - createNodeWithId(nodeId); - - CountDownLatch nodeLocked = new CountDownLatch(1); - AtomicReference otherSessionRef = new AtomicReference<>(); - - try (Session session = neo4j.driver().session(); - Transaction tx = session.beginTransaction()) { - Future txResult = nodeIdUpdater.update(nodeId, newNodeId1, otherSessionRef, nodeLocked); - - Result result = updateNodeId(tx, nodeId, newNodeId2); - result.consume(); - - nodeLocked.countDown(); - // give separate thread some time to block on a lock - Thread.sleep(2_000); - otherSessionRef.get().reset(); - - assertTransactionTerminated(txResult); - tx.commit(); - } - - try (Session session = neo4j.driver().session()) { - Result result = session.run("MATCH (n) RETURN n.id AS id"); - int value = result.single().get("id").asInt(); - assertEquals(newNodeId2, value); - } - } - - private void createNodeWithId(int id) { - try (Session session = neo4j.driver().session()) { - session.run("CREATE (n {id: $id})", parameters("id", id)); - } - } - - private static Result updateNodeId(QueryRunner queryRunner, int currentId, int newId) { - return queryRunner.run( - "MATCH (n {id: $currentId}) SET n.id = $newId", parameters("currentId", currentId, "newId", newId)); - } - - private static void assertTransactionTerminated(Future work) { - ExecutionException e = assertThrows(ExecutionException.class, () -> work.get(20, TimeUnit.SECONDS)); - assertThat(e.getCause(), CoreMatchers.instanceOf(TransientException.class)); - assertThat(e.getCause().getMessage(), startsWith("The transaction has been terminated")); - } - - private void testRandomQueryTermination(boolean autoCommit) throws Exception { - Set runningSessions = newSetFromMap(new ConcurrentHashMap<>()); - AtomicBoolean stop = new AtomicBoolean(); - List> futures = new ArrayList<>(); - - for (int i = 0; i < STRESS_TEST_THREAD_COUNT; i++) { - futures.add(executor.submit(() -> { - ThreadLocalRandom random = ThreadLocalRandom.current(); - while (!stop.get()) { - runRandomQuery(autoCommit, random, runningSessions, stop); - } - })); - } - - long deadline = System.currentTimeMillis() + STRESS_TEST_DURATION_MS; - while (!stop.get()) { - if (System.currentTimeMillis() > deadline) { - stop.set(true); - } - - resetAny(runningSessions); - - MILLISECONDS.sleep(30); - } - - awaitAllFutures(futures); - awaitNoActiveQueries(); - } - - private void runRandomQuery(boolean autoCommit, Random random, Set runningSessions, AtomicBoolean stop) { - try { - Session session = neo4j.driver().session(); - runningSessions.add(session); - try { - String query = STRESS_TEST_QUERIES[random.nextInt(STRESS_TEST_QUERIES.length - 1)]; - runQuery(session, query, autoCommit); - } finally { - runningSessions.remove(session); - session.close(); - } - } catch (Throwable error) { - if (!stop.get() && !isAcceptable(error)) { - stop.set(true); - throw error; - } - // else it is fine to receive some errors from the driver because - // sessions are being reset concurrently by the main thread, driver can also be closed concurrently - } - } - - private void testQueryTermination(String query, boolean autoCommit) { - Future queryResult = runQueryInDifferentThreadAndResetSession(query, autoCommit); - ExecutionException e = assertThrows(ExecutionException.class, () -> queryResult.get(10, SECONDS)); - assertThat(e.getCause(), instanceOf(Neo4jException.class)); - awaitNoActiveQueries(); - } - - private Future runQueryInDifferentThreadAndResetSession(String query, boolean autoCommit) { - AtomicReference sessionRef = new AtomicReference<>(); - - Future queryResult = runAsync(() -> { - Session session = neo4j.driver().session(); - sessionRef.set(session); - runQuery(session, query, autoCommit); - }); - - awaitActiveQueriesToContain(query); - - Session session = sessionRef.get(); - assertNotNull(session); - session.reset(); - - return queryResult; - } - - private static void runQuery(Session session, String query, boolean autoCommit) { - if (autoCommit) { - session.run(query).consume(); - } else { - try (Transaction tx = session.beginTransaction()) { - tx.run(query); - tx.commit(); - } - } - } - - private void awaitNoActiveQueries() { - awaitCondition(() -> activeQueryCount(neo4j.driver()) == 0); - } - - private void awaitActiveQueriesToContain(String value) { - awaitCondition(() -> activeQueryNames(neo4j.driver()).stream().anyMatch(query -> query.contains(value))); - } - - private long countNodes() { - return countNodes(null); - } - - private long countNodes(String label) { - try (Session session = neo4j.driver().session()) { - Result result = - session.run("MATCH (n" + (label == null ? "" : ":" + label) + ") RETURN count(n) AS result"); - return result.single().get(0).asLong(); - } - } - - private static void resetAny(Set sessions) { - sessions.stream().findAny().ifPresent(session -> { - if (sessions.remove(session)) { - resetSafely(session); - } - }); - } - - private static void resetSafely(Session session) { - try { - if (session.isOpen()) { - session.reset(); - } - } catch (ClientException e) { - if (session.isOpen()) { - throw e; - } - // else this thread lost race with close and it's fine - } - } - - private static boolean isAcceptable(Throwable error) { - // get the root cause - while (error.getCause() != null) { - error = error.getCause(); - } - - return isTransactionTerminatedException(error) - || error instanceof ServiceUnavailableException - || error instanceof ClientException - || error instanceof ClosedChannelException; - } - - private static boolean isTransactionTerminatedException(Throwable error) { - return error instanceof TransientException - && error.getMessage().startsWith("The transaction has been terminated") - || error.getMessage().startsWith("Trying to execute query in a terminated transaction"); - } - - private static String longPeriodicCommitQuery() { - URI fileUri = createTmpCsvFile(); - return String.format(LONG_PERIODIC_COMMIT_QUERY_TEMPLATE, fileUri); - } - - private static URI createTmpCsvFile() { - try { - Path importDir = Paths.get(HOME_DIR, TEST_SETTINGS.propertiesMap().get(IMPORT_DIR)); - Path csvFile = Files.createTempFile(importDir, "test", ".csv"); - Iterable lines = range(0, CSV_FILE_SIZE).mapToObj(i -> "Foo-" + i + ", Bar-" + i)::iterator; - return URI.create("file:///" + Files.write(csvFile, lines).getFileName()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private abstract class NodeIdUpdater { - final Future update( - int nodeId, int newNodeId, AtomicReference usedSessionRef, CountDownLatch latchToWait) { - return executor.submit(() -> { - performUpdate(neo4j.driver(), nodeId, newNodeId, usedSessionRef, latchToWait); - return null; - }); - } - - abstract void performUpdate( - Driver driver, - int nodeId, - int newNodeId, - AtomicReference usedSessionRef, - CountDownLatch latchToWait) - throws Exception; - } -} +//package org.neo4j.driver.integration; +// +//import static java.util.Collections.newSetFromMap; +//import static java.util.concurrent.CompletableFuture.runAsync; +//import static java.util.concurrent.TimeUnit.MILLISECONDS; +//import static java.util.concurrent.TimeUnit.MINUTES; +//import static java.util.concurrent.TimeUnit.SECONDS; +//import static java.util.stream.IntStream.range; +//import static org.hamcrest.CoreMatchers.containsString; +//import static org.hamcrest.CoreMatchers.equalTo; +//import static org.hamcrest.CoreMatchers.startsWith; +//import static org.hamcrest.Matchers.greaterThan; +//import static org.hamcrest.Matchers.instanceOf; +//import static org.hamcrest.Matchers.lessThanOrEqualTo; +//import static org.hamcrest.junit.MatcherAssert.assertThat; +//import static org.junit.jupiter.api.Assertions.assertEquals; +//import static org.junit.jupiter.api.Assertions.assertNotNull; +//import static org.junit.jupiter.api.Assertions.assertThrows; +//import static org.junit.jupiter.api.Assertions.assertTrue; +//import static org.neo4j.driver.Values.parameters; +//import static org.neo4j.driver.util.DaemonThreadFactory.daemon; +//import static org.neo4j.driver.util.Neo4jRunner.HOME_DIR; +//import static org.neo4j.driver.util.Neo4jSettings.IMPORT_DIR; +//import static org.neo4j.driver.util.Neo4jSettings.TEST_SETTINGS; +//import static org.neo4j.driver.util.TestUtil.activeQueryCount; +//import static org.neo4j.driver.util.TestUtil.activeQueryNames; +//import static org.neo4j.driver.util.TestUtil.await; +//import static org.neo4j.driver.util.TestUtil.awaitAllFutures; +//import static org.neo4j.driver.util.TestUtil.awaitCondition; +// +//import java.io.IOException; +//import java.io.UncheckedIOException; +//import java.net.URI; +//import java.nio.channels.ClosedChannelException; +//import java.nio.file.Files; +//import java.nio.file.Path; +//import java.nio.file.Paths; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Random; +//import java.util.Set; +//import java.util.concurrent.CompletableFuture; +//import java.util.concurrent.ConcurrentHashMap; +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.ExecutionException; +//import java.util.concurrent.ExecutorService; +//import java.util.concurrent.Executors; +//import java.util.concurrent.Future; +//import java.util.concurrent.ThreadLocalRandom; +//import java.util.concurrent.TimeUnit; +//import java.util.concurrent.atomic.AtomicBoolean; +//import java.util.concurrent.atomic.AtomicInteger; +//import java.util.concurrent.atomic.AtomicLong; +//import java.util.concurrent.atomic.AtomicReference; +//import org.hamcrest.CoreMatchers; +//import org.hamcrest.MatcherAssert; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.RegisterExtension; +//import org.neo4j.driver.Driver; +//import org.neo4j.driver.QueryRunner; +//import org.neo4j.driver.Result; +//import org.neo4j.driver.Session; +//import org.neo4j.driver.Transaction; +//import org.neo4j.driver.exceptions.ClientException; +//import org.neo4j.driver.exceptions.Neo4jException; +//import org.neo4j.driver.exceptions.ServiceUnavailableException; +//import org.neo4j.driver.exceptions.TransientException; +//import org.neo4j.driver.util.DatabaseExtension; +//import org.neo4j.driver.util.ParallelizableIT; +// +//@SuppressWarnings("deprecation") +//@ParallelizableIT +//class SessionResetIT { +// private static final int CSV_FILE_SIZE = 10_000; +// private static final int LOAD_CSV_BATCH_SIZE = 10; +// +// private static final String SHORT_QUERY_1 = "CREATE (n:Node {name: 'foo', occupation: 'bar'})"; +// private static final String SHORT_QUERY_2 = "MATCH (n:Node {name: 'foo'}) RETURN count(n)"; +// private static final String LONG_QUERY = "UNWIND range(0, 10000000) AS i CREATE (n:Node {idx: i}) DELETE n"; +// private static final String LONG_PERIODIC_COMMIT_QUERY_TEMPLATE = "USING PERIODIC COMMIT 1 " +// + "LOAD CSV FROM '%s' AS line " +// + "UNWIND range(1, " +// + LOAD_CSV_BATCH_SIZE + ") AS index " + "CREATE (n:Node {id: index, name: line[0], occupation: line[1]})"; +// +// private static final int STRESS_TEST_THREAD_COUNT = Runtime.getRuntime().availableProcessors() * 2; +// private static final long STRESS_TEST_DURATION_MS = SECONDS.toMillis(5); +// private static final String[] STRESS_TEST_QUERIES = {SHORT_QUERY_1, SHORT_QUERY_2, LONG_QUERY}; +// +// @RegisterExtension +// static final DatabaseExtension neo4j = new DatabaseExtension(); +// +// private ExecutorService executor; +// +// @BeforeEach +// void setUp() { +// executor = Executors.newCachedThreadPool(daemon(getClass().getSimpleName() + "-thread")); +// } +// +// @AfterEach +// void tearDown() { +// if (executor != null) { +// executor.shutdownNow(); +// } +// } +// +// @Test +// void shouldTerminateAutoCommitQuery() { +// testQueryTermination(LONG_QUERY, true); +// } +// +// @Test +// void shouldTerminateQueryInUnmanagedTransaction() { +// testQueryTermination(LONG_QUERY, false); +// } +// +// /** +// * It is currently unsafe to terminate periodic commit query because it'll then be half-committed. +// * So the driver give no guarantee when the periodic commit could be terminated. +// * For a user who want to terminate a periodic commit, he or she should use kill query by id. +// */ +// @Test +// void shouldTerminatePeriodicCommitQueryRandomly() { +// Future queryResult = runQueryInDifferentThreadAndResetSession(longPeriodicCommitQuery(), true); +// +// ExecutionException e = assertThrows(ExecutionException.class, () -> queryResult.get(1, MINUTES)); +// assertThat(e.getCause(), instanceOf(Neo4jException.class)); +// +// awaitNoActiveQueries(); +// +// assertThat(countNodes(), lessThanOrEqualTo(((long) CSV_FILE_SIZE) * LOAD_CSV_BATCH_SIZE)); +// } +// +// @Test +// void shouldTerminateAutoCommitQueriesRandomly() throws Exception { +// testRandomQueryTermination(true); +// } +// +// @Test +// void shouldTerminateQueriesInUnmanagedTransactionsRandomly() throws Exception { +// testRandomQueryTermination(false); +// } +// +// @Test +// void shouldRejectNewTransactionWhenOpenTransactionExistsAndShouldFailRunResultOnSessionReset() throws Throwable { +// neo4j.ensureProcedures("longRunningStatement.jar"); +// +// try (Session session = neo4j.driver().session()) { +// Transaction tx1 = session.beginTransaction(); +// +// CompletableFuture txRunFuture = CompletableFuture.runAsync( +// () -> tx1.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); +// +// awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); +// session.reset(); +// +// ClientException e1 = assertThrows(ClientException.class, session::beginTransaction); +// assertThat( +// e1.getMessage(), +// containsString("You cannot begin a transaction on a session with an open transaction")); +// +// ClientException e2 = assertThrows(ClientException.class, () -> tx1.run("RETURN 1")); +// assertThat(e2.getMessage(), containsString("Cannot run more queries in this transaction")); +// +// // Make sure failure from the terminated long running query is propagated +// Neo4jException e3 = assertThrows(Neo4jException.class, () -> await(txRunFuture)); +// assertThat(e3.getMessage(), containsString("The transaction has been terminated")); +// } +// } +// +// @Test +// void shouldSuccessfullyCloseAfterSessionReset() throws Throwable { +// neo4j.ensureProcedures("longRunningStatement.jar"); +// +// try (Session session = neo4j.driver().session()) { +// CompletableFuture.runAsync( +// () -> session.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); +// +// awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); +// session.reset(); +// } +// } +// +// @Test +// void shouldBeAbleToBeginNewTransactionAfterFirstTransactionInterruptedBySessionResetIsClosed() throws Throwable { +// neo4j.ensureProcedures("longRunningStatement.jar"); +// +// try (Session session = neo4j.driver().session()) { +// Transaction tx1 = session.beginTransaction(); +// +// CompletableFuture txRunFuture = runAsync( +// () -> tx1.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); +// +// awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); +// session.reset(); +// +// Neo4jException e = assertThrows(Neo4jException.class, () -> await(txRunFuture)); +// assertThat(e.getMessage(), containsString("The transaction has been terminated")); +// tx1.close(); +// +// try (Transaction tx2 = session.beginTransaction()) { +// tx2.run("CREATE (n:FirstNode)"); +// tx2.commit(); +// } +// +// Result result = session.run("MATCH (n) RETURN count(n)"); +// long nodes = result.single().get("count(n)").asLong(); +// MatcherAssert.assertThat(nodes, equalTo(1L)); +// } +// } +// +// @Test +// void shouldKillLongRunningQuery() throws Throwable { +// neo4j.ensureProcedures("longRunningStatement.jar"); +// +// final int executionTimeout = 10; // 10s +// final int killTimeout = 1; // 1s +// final AtomicLong startTime = new AtomicLong(-1); +// long endTime; +// +// try (Session session = neo4j.driver().session()) { +// CompletableFuture sessionRunFuture = CompletableFuture.runAsync(() -> { +// // When +// startTime.set(System.currentTimeMillis()); +// session.run( +// "CALL test.driver.longRunningStatement({seconds})", parameters("seconds", executionTimeout)); +// }); +// +// resetSessionAfterTimeout(session, killTimeout); +// +// assertThrows(Neo4jException.class, () -> await(sessionRunFuture)); +// } +// +// endTime = System.currentTimeMillis(); +// assertTrue(startTime.get() > 0); +// assertTrue(endTime - startTime.get() > killTimeout * 1000); // get reset by session.reset +// assertTrue(endTime - startTime.get() < executionTimeout * 1000 / 2); // finished before execution finished +// } +// +// @Test +// void shouldKillLongStreamingResult() throws Throwable { +// neo4j.ensureProcedures("longRunningStatement.jar"); +// // Given +// final int executionTimeout = 10; // 10s +// final int killTimeout = 1; // 1s +// final AtomicInteger recordCount = new AtomicInteger(); +// final AtomicLong startTime = new AtomicLong(-1); +// long endTime; +// +// Neo4jException e = assertThrows(Neo4jException.class, () -> { +// try (Session session = neo4j.driver().session()) { +// Result result = session.run( +// "CALL test.driver.longStreamingResult({seconds})", parameters("seconds", executionTimeout)); +// +// resetSessionAfterTimeout(session, killTimeout); +// +// // When +// startTime.set(System.currentTimeMillis()); +// while (result.hasNext()) { +// result.next(); +// recordCount.incrementAndGet(); +// } +// } +// }); +// +// endTime = System.currentTimeMillis(); +// assertThat(e.getMessage(), containsString("The transaction has been terminated")); +// assertThat(recordCount.get(), greaterThan(1)); +// +// assertTrue(startTime.get() > 0); +// assertTrue(endTime - startTime.get() > killTimeout * 1000); // get reset by session.reset +// assertTrue(endTime - startTime.get() < executionTimeout * 1000 / 2); // finished before execution finished +// } +// +// private void resetSessionAfterTimeout(Session session, int timeout) { +// executor.submit(() -> { +// try { +// Thread.sleep(timeout * 1000); // let the query execute for timeout seconds +// } catch (InterruptedException ignore) { +// } finally { +// session.reset(); // reset the session after timeout +// } +// }); +// } +// +// @Test +// void shouldAllowMoreQueriesAfterSessionReset() { +// // Given +// try (Session session = neo4j.driver().session()) { +// +// session.run("RETURN 1").consume(); +// +// // When reset the state of this session +// session.reset(); +// +// // Then can run successfully more queries without any error +// session.run("RETURN 2").consume(); +// } +// } +// +// @Test +// void shouldAllowMoreTxAfterSessionReset() { +// // Given +// try (Session session = neo4j.driver().session()) { +// try (Transaction tx = session.beginTransaction()) { +// tx.run("RETURN 1"); +// tx.commit(); +// } +// +// // When reset the state of this session +// session.reset(); +// +// // Then can run more Tx +// try (Transaction tx = session.beginTransaction()) { +// tx.run("RETURN 2"); +// tx.commit(); +// } +// } +// } +// +// @Test +// void shouldMarkTxAsFailedAndDisallowRunAfterSessionReset() { +// // Given +// try (Session session = neo4j.driver().session()) { +// Transaction tx = session.beginTransaction(); +// // When reset the state of this session +// session.reset(); +// +// // Then +// Exception e = assertThrows(Exception.class, () -> { +// tx.run("RETURN 1"); +// tx.commit(); +// }); +// assertThat(e.getMessage(), startsWith("Cannot run more queries in this transaction")); +// } +// } +// +// @Test +// void shouldAllowMoreTxAfterSessionResetInTx() { +// // Given +// try (Session session = neo4j.driver().session()) { +// try (Transaction ignore = session.beginTransaction()) { +// // When reset the state of this session +// session.reset(); +// } +// +// // Then can run more Tx +// try (Transaction tx = session.beginTransaction()) { +// tx.run("RETURN 2"); +// tx.commit(); +// } +// } +// } +// +// @Test +// void resetShouldStopQueryWaitingForALock() throws Exception { +// testResetOfQueryWaitingForLock(new NodeIdUpdater() { +// @Override +// void performUpdate( +// Driver driver, +// int nodeId, +// int newNodeId, +// AtomicReference usedSessionRef, +// CountDownLatch latchToWait) +// throws Exception { +// try (Session session = driver.session()) { +// usedSessionRef.set(session); +// latchToWait.await(); +// Result result = updateNodeId(session, nodeId, newNodeId); +// result.consume(); +// } +// } +// }); +// } +// +// @Test +// void resetShouldStopTransactionWaitingForALock() throws Exception { +// testResetOfQueryWaitingForLock(new NodeIdUpdater() { +// @Override +// public void performUpdate( +// Driver driver, +// int nodeId, +// int newNodeId, +// AtomicReference usedSessionRef, +// CountDownLatch latchToWait) +// throws Exception { +// try (Session session = neo4j.driver().session(); +// Transaction tx = session.beginTransaction()) { +// usedSessionRef.set(session); +// latchToWait.await(); +// Result result = updateNodeId(tx, nodeId, newNodeId); +// result.consume(); +// } +// } +// }); +// } +// +// @Test +// void resetShouldStopWriteTransactionWaitingForALock() throws Exception { +// AtomicInteger invocationsOfWork = new AtomicInteger(); +// +// testResetOfQueryWaitingForLock(new NodeIdUpdater() { +// @Override +// public void performUpdate( +// Driver driver, +// int nodeId, +// int newNodeId, +// AtomicReference usedSessionRef, +// CountDownLatch latchToWait) +// throws Exception { +// try (Session session = driver.session()) { +// usedSessionRef.set(session); +// latchToWait.await(); +// +// session.writeTransaction(tx -> { +// invocationsOfWork.incrementAndGet(); +// Result result = updateNodeId(tx, nodeId, newNodeId); +// result.consume(); +// return null; +// }); +// } +// } +// }); +// +// assertEquals(1, invocationsOfWork.get()); +// } +// +// @Test +// void shouldBeAbleToRunMoreQueriesAfterResetOnNoErrorState() { +// try (Session session = neo4j.driver().session()) { +// // Given +// session.reset(); +// +// // When +// Transaction tx = session.beginTransaction(); +// tx.run("CREATE (n:FirstNode)"); +// tx.commit(); +// +// // Then the outcome of both queries should be visible +// Result result = session.run("MATCH (n) RETURN count(n)"); +// long nodes = result.single().get("count(n)").asLong(); +// assertThat(nodes, equalTo(1L)); +// } +// } +// +// @Test +// void shouldHandleResetBeforeRun() { +// try (Session session = neo4j.driver().session(); +// Transaction tx = session.beginTransaction()) { +// session.reset(); +// +// ClientException e = assertThrows(ClientException.class, () -> tx.run("CREATE (n:FirstNode)")); +// assertThat(e.getMessage(), containsString("Cannot run more queries in this transaction")); +// } +// } +// +// @Test +// void shouldHandleResetFromMultipleThreads() throws Throwable { +// Session session = neo4j.driver().session(); +// +// CountDownLatch beforeCommit = new CountDownLatch(1); +// CountDownLatch afterReset = new CountDownLatch(1); +// +// Future txFuture = executor.submit(() -> { +// Transaction tx1 = session.beginTransaction(); +// tx1.run("CREATE (n:FirstNode)"); +// beforeCommit.countDown(); +// afterReset.await(); +// +// // session has been reset, it should not be possible to commit the transaction +// try { +// tx1.commit(); +// } catch (Neo4jException ignore) { +// } +// +// try (Transaction tx2 = session.beginTransaction()) { +// tx2.run("CREATE (n:SecondNode)"); +// tx2.commit(); +// } +// +// return null; +// }); +// +// Future resetFuture = executor.submit(() -> { +// beforeCommit.await(); +// session.reset(); +// afterReset.countDown(); +// return null; +// }); +// +// executor.shutdown(); +// executor.awaitTermination(20, SECONDS); +// +// txFuture.get(20, SECONDS); +// resetFuture.get(20, SECONDS); +// +// assertEquals(0, countNodes("FirstNode")); +// assertEquals(1, countNodes("SecondNode")); +// } +// +// private void testResetOfQueryWaitingForLock(NodeIdUpdater nodeIdUpdater) throws Exception { +// int nodeId = 42; +// int newNodeId1 = 4242; +// int newNodeId2 = 424242; +// +// createNodeWithId(nodeId); +// +// CountDownLatch nodeLocked = new CountDownLatch(1); +// AtomicReference otherSessionRef = new AtomicReference<>(); +// +// try (Session session = neo4j.driver().session(); +// Transaction tx = session.beginTransaction()) { +// Future txResult = nodeIdUpdater.update(nodeId, newNodeId1, otherSessionRef, nodeLocked); +// +// Result result = updateNodeId(tx, nodeId, newNodeId2); +// result.consume(); +// +// nodeLocked.countDown(); +// // give separate thread some time to block on a lock +// Thread.sleep(2_000); +// otherSessionRef.get().reset(); +// +// assertTransactionTerminated(txResult); +// tx.commit(); +// } +// +// try (Session session = neo4j.driver().session()) { +// Result result = session.run("MATCH (n) RETURN n.id AS id"); +// int value = result.single().get("id").asInt(); +// assertEquals(newNodeId2, value); +// } +// } +// +// private void createNodeWithId(int id) { +// try (Session session = neo4j.driver().session()) { +// session.run("CREATE (n {id: $id})", parameters("id", id)); +// } +// } +// +// private static Result updateNodeId(QueryRunner queryRunner, int currentId, int newId) { +// return queryRunner.run( +// "MATCH (n {id: $currentId}) SET n.id = $newId", parameters("currentId", currentId, "newId", newId)); +// } +// +// private static void assertTransactionTerminated(Future work) { +// ExecutionException e = assertThrows(ExecutionException.class, () -> work.get(20, TimeUnit.SECONDS)); +// assertThat(e.getCause(), CoreMatchers.instanceOf(TransientException.class)); +// assertThat(e.getCause().getMessage(), startsWith("The transaction has been terminated")); +// } +// +// private void testRandomQueryTermination(boolean autoCommit) throws Exception { +// Set runningSessions = newSetFromMap(new ConcurrentHashMap<>()); +// AtomicBoolean stop = new AtomicBoolean(); +// List> futures = new ArrayList<>(); +// +// for (int i = 0; i < STRESS_TEST_THREAD_COUNT; i++) { +// futures.add(executor.submit(() -> { +// ThreadLocalRandom random = ThreadLocalRandom.current(); +// while (!stop.get()) { +// runRandomQuery(autoCommit, random, runningSessions, stop); +// } +// })); +// } +// +// long deadline = System.currentTimeMillis() + STRESS_TEST_DURATION_MS; +// while (!stop.get()) { +// if (System.currentTimeMillis() > deadline) { +// stop.set(true); +// } +// +// resetAny(runningSessions); +// +// MILLISECONDS.sleep(30); +// } +// +// awaitAllFutures(futures); +// awaitNoActiveQueries(); +// } +// +// private void runRandomQuery(boolean autoCommit, Random random, Set runningSessions, AtomicBoolean stop) { +// try { +// Session session = neo4j.driver().session(); +// runningSessions.add(session); +// try { +// String query = STRESS_TEST_QUERIES[random.nextInt(STRESS_TEST_QUERIES.length - 1)]; +// runQuery(session, query, autoCommit); +// } finally { +// runningSessions.remove(session); +// session.close(); +// } +// } catch (Throwable error) { +// if (!stop.get() && !isAcceptable(error)) { +// stop.set(true); +// throw error; +// } +// // else it is fine to receive some errors from the driver because +// // sessions are being reset concurrently by the main thread, driver can also be closed concurrently +// } +// } +// +// private void testQueryTermination(String query, boolean autoCommit) { +// Future queryResult = runQueryInDifferentThreadAndResetSession(query, autoCommit); +// ExecutionException e = assertThrows(ExecutionException.class, () -> queryResult.get(10, SECONDS)); +// assertThat(e.getCause(), instanceOf(Neo4jException.class)); +// awaitNoActiveQueries(); +// } +// +// private Future runQueryInDifferentThreadAndResetSession(String query, boolean autoCommit) { +// AtomicReference sessionRef = new AtomicReference<>(); +// +// Future queryResult = runAsync(() -> { +// Session session = neo4j.driver().session(); +// sessionRef.set(session); +// runQuery(session, query, autoCommit); +// }); +// +// awaitActiveQueriesToContain(query); +// +// Session session = sessionRef.get(); +// assertNotNull(session); +// session.reset(); +// +// return queryResult; +// } +// +// private static void runQuery(Session session, String query, boolean autoCommit) { +// if (autoCommit) { +// session.run(query).consume(); +// } else { +// try (Transaction tx = session.beginTransaction()) { +// tx.run(query); +// tx.commit(); +// } +// } +// } +// +// private void awaitNoActiveQueries() { +// awaitCondition(() -> activeQueryCount(neo4j.driver()) == 0); +// } +// +// private void awaitActiveQueriesToContain(String value) { +// awaitCondition(() -> activeQueryNames(neo4j.driver()).stream().anyMatch(query -> query.contains(value))); +// } +// +// private long countNodes() { +// return countNodes(null); +// } +// +// private long countNodes(String label) { +// try (Session session = neo4j.driver().session()) { +// Result result = +// session.run("MATCH (n" + (label == null ? "" : ":" + label) + ") RETURN count(n) AS result"); +// return result.single().get(0).asLong(); +// } +// } +// +// private static void resetAny(Set sessions) { +// sessions.stream().findAny().ifPresent(session -> { +// if (sessions.remove(session)) { +// resetSafely(session); +// } +// }); +// } +// +// private static void resetSafely(Session session) { +// try { +// if (session.isOpen()) { +// session.reset(); +// } +// } catch (ClientException e) { +// if (session.isOpen()) { +// throw e; +// } +// // else this thread lost race with close and it's fine +// } +// } +// +// private static boolean isAcceptable(Throwable error) { +// // get the root cause +// while (error.getCause() != null) { +// error = error.getCause(); +// } +// +// return isTransactionTerminatedException(error) +// || error instanceof ServiceUnavailableException +// || error instanceof ClientException +// || error instanceof ClosedChannelException; +// } +// +// private static boolean isTransactionTerminatedException(Throwable error) { +// return error instanceof TransientException +// && error.getMessage().startsWith("The transaction has been terminated") +// || error.getMessage().startsWith("Trying to execute query in a terminated transaction"); +// } +// +// private static String longPeriodicCommitQuery() { +// URI fileUri = createTmpCsvFile(); +// return String.format(LONG_PERIODIC_COMMIT_QUERY_TEMPLATE, fileUri); +// } +// +// private static URI createTmpCsvFile() { +// try { +// Path importDir = Paths.get(HOME_DIR, TEST_SETTINGS.propertiesMap().get(IMPORT_DIR)); +// Path csvFile = Files.createTempFile(importDir, "test", ".csv"); +// Iterable lines = range(0, CSV_FILE_SIZE).mapToObj(i -> "Foo-" + i + ", Bar-" + i)::iterator; +// return URI.create("file:///" + Files.write(csvFile, lines).getFileName()); +// } catch (IOException e) { +// throw new UncheckedIOException(e); +// } +// } +// +// private abstract class NodeIdUpdater { +// final Future update( +// int nodeId, int newNodeId, AtomicReference usedSessionRef, CountDownLatch latchToWait) { +// return executor.submit(() -> { +// performUpdate(neo4j.driver(), nodeId, newNodeId, usedSessionRef, latchToWait); +// return null; +// }); +// } +// +// abstract void performUpdate( +// Driver driver, +// int nodeId, +// int newNodeId, +// AtomicReference usedSessionRef, +// CountDownLatch latchToWait) +// throws Exception; +// } +//} diff --git a/driver/src/test/java/org/neo4j/driver/integration/TrustCustomCertificateIT.java b/driver/src/test/java/org/neo4j/driver/integration/TrustCustomCertificateIT.java index ca592292ab..b327ac1250 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/TrustCustomCertificateIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/TrustCustomCertificateIT.java @@ -24,6 +24,7 @@ import static org.neo4j.driver.Config.TrustStrategy.trustCustomCertificateSignedBy; import static org.neo4j.driver.util.CertificateUtil.createNewCertificateAndKey; import static org.neo4j.driver.util.CertificateUtil.createNewCertificateAndKeySignedBy; +import static org.neo4j.driver.util.DatabaseExtension.getDockerHostGeneralName; import java.io.File; import java.util.function.Supplier; @@ -35,7 +36,6 @@ import org.neo4j.driver.Result; import org.neo4j.driver.Session; import org.neo4j.driver.exceptions.SecurityException; -import org.neo4j.driver.util.CertificateExtension; import org.neo4j.driver.util.CertificateUtil.CertificateKeyPair; import org.neo4j.driver.util.DatabaseExtension; import org.neo4j.driver.util.ParallelizableIT; @@ -43,7 +43,7 @@ @ParallelizableIT class TrustCustomCertificateIT { @RegisterExtension - static final DatabaseExtension neo4j = new CertificateExtension(); + static final DatabaseExtension neo4j = new DatabaseExtension(); @Test void shouldAcceptServerWithCertificateSignedByDriverCertificate() throws Throwable { @@ -51,7 +51,7 @@ void shouldAcceptServerWithCertificateSignedByDriverCertificate() throws Throwab CertificateKeyPair root = createNewCertificateAndKey(); // When - CertificateKeyPair server = createNewCertificateAndKeySignedBy(root); + CertificateKeyPair server = createNewCertificateAndKeySignedBy(root, getDockerHostGeneralName()); neo4j.updateEncryptionKeyAndCert(server.key(), server.cert()); // Then @@ -59,7 +59,7 @@ void shouldAcceptServerWithCertificateSignedByDriverCertificate() throws Throwab } @Test - void shouldAcceptServerWithSameCertificate() throws Throwable { + void shouldAcceptServerWithSameCertificate() { shouldBeAbleToRunCypher(() -> createDriverWithCustomCertificate(neo4j.tlsCertFile())); } @@ -70,7 +70,7 @@ void shouldRejectServerWithUntrustedCertificate() throws Throwable { // When & Then final Driver driver = createDriverWithCustomCertificate(certificateAndKey.cert()); - SecurityException error = assertThrows(SecurityException.class, driver::verifyConnectivity); + assertThrows(SecurityException.class, driver::verifyConnectivity); } private void shouldBeAbleToRunCypher(Supplier driverSupplier) { diff --git a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionServerRestartIT.java b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionServerRestartIT.java index 7370eb0d0d..0db23e4ec6 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionServerRestartIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionServerRestartIT.java @@ -67,26 +67,26 @@ void shouldFailWhenServerIsRestarted() { ResultCursor cursor = await(session.runAsync(query)); if (i == 0) { - neo4j.stopDb(); + neo4j.stopProxy(); } List records = await(cursor.listAsync()); assertEquals(100, records.size()); } }); - neo4j.startDb(); + neo4j.startProxy(); } @Test void shouldRunAfterRunFailureToAcquireConnection() { - neo4j.stopDb(); + neo4j.stopProxy(); assertThrows(ServiceUnavailableException.class, () -> { ResultCursor cursor = await(session.runAsync("RETURN 42")); await(cursor.nextAsync()); }); - neo4j.startDb(); + neo4j.startProxy(); ResultCursor cursor2 = await(session.runAsync("RETURN 42")); Record record = await(cursor2.singleAsync()); @@ -95,14 +95,14 @@ void shouldRunAfterRunFailureToAcquireConnection() { @Test void shouldBeginTxAfterRunFailureToAcquireConnection() { - neo4j.stopDb(); + neo4j.stopProxy(); assertThrows(ServiceUnavailableException.class, () -> { ResultCursor cursor = await(session.runAsync("RETURN 42")); await(cursor.consumeAsync()); }); - neo4j.startDb(); + neo4j.startProxy(); AsyncTransaction tx = await(session.beginTransactionAsync()); ResultCursor cursor2 = await(tx.runAsync("RETURN 42")); diff --git a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java index b1e95d2d45..b8831bfa89 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java @@ -508,7 +508,7 @@ void shouldFailToCommitWhenServerIsRestarted() { await(tx.runAsync("CREATE ()")); - neo4j.stopDb(); + neo4j.stopProxy(); assertThrows(ServiceUnavailableException.class, () -> await(tx.commitAsync())); } diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java index 5c5336a682..b77d9f7ddb 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java @@ -549,7 +549,7 @@ void shouldFailToCommitWhenServerIsRestarted() { assertThrows(ServiceUnavailableException.class, () -> { await(Flux.from(result.records()).doOnSubscribe(subscription -> { - neo4j.stopDb(); + neo4j.stopProxy(); })); await(tx.commit()); }); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java index ea974d06fd..c15022ffec 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java @@ -55,7 +55,6 @@ import org.neo4j.driver.internal.util.FakeClock; import org.neo4j.driver.internal.util.ImmediateSchedulingEventExecutor; import org.neo4j.driver.util.DatabaseExtension; -import org.neo4j.driver.util.Neo4jRunner; import org.neo4j.driver.util.ParallelizableIT; @ParallelizableIT @@ -122,7 +121,7 @@ void shouldAllowAcquireAfterFailures() throws Exception { AuthenticationException e = assertThrows(AuthenticationException.class, () -> acquire(pool)); } - authTokenMap.put("credentials", value(Neo4jRunner.PASSWORD)); + authTokenMap.put("credentials", value(neo4j.adminPassword())); assertNotNull(acquire(pool)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/Neo4jWithFeatureCondition.java b/driver/src/test/java/org/neo4j/driver/internal/util/Neo4jWithFeatureCondition.java index 0fb5f737b3..9ca28bf30c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/Neo4jWithFeatureCondition.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/Neo4jWithFeatureCondition.java @@ -28,8 +28,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.neo4j.driver.Driver; import org.neo4j.driver.Session; -import org.neo4j.driver.util.Neo4jRunner; -import org.neo4j.driver.util.Neo4jSettings; +import org.neo4j.driver.util.DatabaseExtension; public class Neo4jWithFeatureCondition implements ExecutionCondition { private static final ConditionEvaluationResult ENABLED_NOT_ANNOTATED = @@ -61,7 +60,7 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con } private static ConditionEvaluationResult checkFeatureAvailability(Neo4jFeature feature, boolean negated) { - Driver driver = getSharedNeo4jDriver(); + Driver driver = DatabaseExtension.getInstance().driver(); if (driver != null) { ServerVersion version = ServerVersion.version(driver); return createResult(version, feature, negated); @@ -74,7 +73,7 @@ private static ConditionEvaluationResult checkEditionAvailability( if (previousResult.isDisabled()) { return previousResult; } - Driver driver = getSharedNeo4jDriver(); + Driver driver = DatabaseExtension.getInstance().driver(); if (driver != null) { try (Session session = driver.session()) { String value = session.run("CALL dbms.components() YIELD edition") @@ -108,17 +107,4 @@ private static ConditionEvaluationResult createResult( : disabled("Disabled on neo4j " + version + " because it does not support " + feature); } } - - private static Driver getSharedNeo4jDriver() { - try { - Neo4jRunner runner = Neo4jRunner.getOrCreateGlobalRunner(); - // ensure database is running with default credentials - runner.ensureRunning(Neo4jSettings.TEST_SETTINGS); - return runner.driver(); - } catch (Throwable t) { - System.err.println("Failed to check database version in the test execution condition"); - t.printStackTrace(); - return null; - } - } } diff --git a/driver/src/test/java/org/neo4j/driver/util/CertificateExtension.java b/driver/src/test/java/org/neo4j/driver/util/CertificateExtension.java deleted file mode 100644 index c654729d37..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/CertificateExtension.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util; - -import static org.neo4j.driver.util.Neo4jRunner.debug; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.Arrays; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -public class CertificateExtension extends DatabaseExtension implements AfterEachCallback { - private Path originalKeyFile; - private Path originalCertFile; - - @Override - public void beforeEach(ExtensionContext context) throws Exception { - super.beforeEach(context); - - originalKeyFile = Files.createTempFile("key-file-", ""); - originalCertFile = Files.createTempFile("cert-file-", ""); - - Files.copy(tlsKeyFile().toPath(), originalKeyFile, StandardCopyOption.REPLACE_EXISTING); - Files.copy(tlsCertFile().toPath(), originalCertFile, StandardCopyOption.REPLACE_EXISTING); - } - - @Override - public void afterEach(ExtensionContext context) throws Exception { - // if the key and cert file changed, then we restore the file and restart the server. - if (!smallFileContentEquals(tlsKeyFile().toPath(), originalKeyFile) - || !smallFileContentEquals(tlsCertFile().toPath(), originalCertFile)) { - debug("Restoring original key and certificate file after certificate test."); - updateEncryptionKeyAndCert(originalKeyFile.toFile(), originalCertFile.toFile()); - } - Files.deleteIfExists(originalKeyFile); - Files.deleteIfExists(originalCertFile); - } - - private boolean smallFileContentEquals(Path path, Path pathAnother) throws IOException { - byte[] fileContent = Files.readAllBytes(path); - byte[] fileContentAnother = Files.readAllBytes(pathAnother); - return Arrays.equals(fileContent, fileContentAnother); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/CertificateUtil.java b/driver/src/test/java/org/neo4j/driver/util/CertificateUtil.java index f987b5bd5f..b84181c1c2 100644 --- a/driver/src/test/java/org/neo4j/driver/util/CertificateUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/CertificateUtil.java @@ -37,8 +37,11 @@ import java.security.Security; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Date; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.BasicConstraints; @@ -79,7 +82,7 @@ private static KeyPair generateKeyPair() throws NoSuchProviderException, NoSuchA } private static X509Certificate generateCert( - X500Name issuer, X500Name subject, KeyPair issuerKeys, PublicKey publicKey) + X500Name issuer, X500Name subject, KeyPair issuerKeys, PublicKey publicKey, GeneralName... generalNames) throws GeneralSecurityException, OperatorCreationException, CertIOException { // Create x509 certificate Date startDate = new Date(System.currentTimeMillis()); @@ -89,7 +92,10 @@ private static X509Certificate generateCert( new JcaX509v3CertificateBuilder(issuer, serialNum, startDate, endDate, subject, publicKey); // Subject alternative name (part of SNI extension, used for hostname verification) - GeneralNames subjectAlternativeName = new GeneralNames(new GeneralName(GeneralName.dNSName, DEFAULT_HOST_NAME)); + Set names = new HashSet<>(); + names.add(new GeneralName(GeneralName.dNSName, DEFAULT_HOST_NAME)); + names.addAll(Arrays.asList(generalNames)); + GeneralNames subjectAlternativeName = new GeneralNames(names.toArray(new GeneralName[0])); certBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAlternativeName); certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true)); @@ -107,7 +113,7 @@ public static class SelfSignedCertificateGenerator { private final KeyPair keyPair; private final X509Certificate certificate; - public SelfSignedCertificateGenerator() + public SelfSignedCertificateGenerator(GeneralName... generalNames) throws GeneralSecurityException, OperatorCreationException, CertIOException { // Create the public/private rsa key pair keyPair = generateKeyPair(); @@ -117,7 +123,8 @@ public SelfSignedCertificateGenerator() new X500Name("CN=" + DEFAULT_HOST_NAME), new X500Name("CN=" + DEFAULT_HOST_NAME), keyPair, - keyPair.getPublic()); + keyPair.getPublic(), + generalNames); } public void savePrivateKey(File saveTo) throws IOException { @@ -128,14 +135,15 @@ public void saveSelfSignedCertificate(File saveTo) throws CertificateEncodingExc writePem("CERTIFICATE", certificate.getEncoded(), saveTo); } - public X509Certificate sign(PKCS10CertificationRequest csr, PublicKey csrPublicKey) + public X509Certificate sign(PKCS10CertificationRequest csr, PublicKey csrPublicKey, GeneralName... generalNames) throws GeneralSecurityException, OperatorCreationException, CertIOException { return generateCert( X500Name.getInstance( this.certificate.getSubjectX500Principal().getEncoded()), csr.getSubject(), keyPair, - csrPublicKey); + csrPublicKey, + generalNames); } } @@ -195,26 +203,27 @@ private static void writePem(String type, byte[] encodedContent, File path) thro } } - public static CertificateKeyPair createNewCertificateAndKeySignedBy(CertificateKeyPair root) - throws Throwable { + public static CertificateKeyPair createNewCertificateAndKeySignedBy( + CertificateKeyPair root, GeneralName... generalNames) throws Throwable { Objects.requireNonNull(root.certGenerator); File cert = tempFile("driver", ".cert"); File key = tempFile("driver", ".key"); CertificateUtil.CertificateSigningRequestGenerator csrGenerator = new CertificateUtil.CertificateSigningRequestGenerator(); - X509Certificate signedCert = - root.certGenerator.sign(csrGenerator.certificateSigningRequest(), csrGenerator.publicKey()); + X509Certificate signedCert = root.certGenerator.sign( + csrGenerator.certificateSigningRequest(), csrGenerator.publicKey(), generalNames); csrGenerator.savePrivateKey(key); saveX509Cert(signedCert, cert); return new CertificateKeyPair<>(cert, key); } - public static CertificateKeyPair createNewCertificateAndKey() throws Throwable { + public static CertificateKeyPair createNewCertificateAndKey(GeneralName... ipAddresses) + throws Throwable { File cert = tempFile("driver", ".cert"); File key = tempFile("driver", ".key"); CertificateUtil.SelfSignedCertificateGenerator certGenerator = - new CertificateUtil.SelfSignedCertificateGenerator(); + new CertificateUtil.SelfSignedCertificateGenerator(ipAddresses); certGenerator.saveSelfSignedCertificate(cert); certGenerator.savePrivateKey(key); diff --git a/driver/src/test/java/org/neo4j/driver/util/DatabaseExtension.java b/driver/src/test/java/org/neo4j/driver/util/DatabaseExtension.java index 929770eabf..c0eea975f1 100644 --- a/driver/src/test/java/org/neo4j/driver/util/DatabaseExtension.java +++ b/driver/src/test/java/org/neo4j/driver/util/DatabaseExtension.java @@ -18,141 +18,342 @@ */ package org.neo4j.driver.util; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.neo4j.driver.util.Neo4jRunner.DEFAULT_AUTH_TOKEN; -import static org.neo4j.driver.util.Neo4jRunner.HOME_DIR; -import static org.neo4j.driver.util.Neo4jRunner.debug; -import static org.neo4j.driver.util.Neo4jRunner.getOrCreateGlobalRunner; -import static org.neo4j.driver.util.Neo4jSettings.DEFAULT_TLS_CERT_PATH; -import static org.neo4j.driver.util.Neo4jSettings.DEFAULT_TLS_KEY_PATH; +import static java.lang.Integer.parseInt; +import static org.neo4j.driver.util.Neo4jSettings.BOLT_TLS_LEVEL; +import static org.neo4j.driver.util.Neo4jSettings.BoltTlsLevel.OPTIONAL; +import static org.neo4j.driver.util.Neo4jSettings.BoltTlsLevel.REQUIRED; +import static org.neo4j.driver.util.Neo4jSettings.SSL_POLICY_BOLT_CLIENT_AUTH; +import static org.neo4j.driver.util.Neo4jSettings.SSL_POLICY_BOLT_ENABLED; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.net.URI; -import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.bouncycastle.asn1.x509.GeneralName; import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Session; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.types.TypeSystem; +import org.neo4j.driver.util.CertificateUtil.CertificateKeyPair; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; -public class DatabaseExtension implements BeforeEachCallback, AfterAllCallback { - static final String TEST_RESOURCE_FOLDER_PATH = "src/test/resources"; +public class DatabaseExtension implements ExecutionCondition, BeforeEachCallback, AfterEachCallback, AfterAllCallback { + private static final int BOLT_PORT = 7687; + private static final int HTTP_PORT = 7474; - private final Neo4jSettings settings; - private Neo4jRunner runner; + private static final boolean dockerAvailable; + private static final DatabaseExtension instance; + private static final URI boltUri; + private static final URI httpUri; + private static final AuthToken authToken; + private static final File cert; + private static final File key; + private static final Network network; + private static final GenericContainer nginx; + private static final Map defaultConfig; - public DatabaseExtension() { - this(Neo4jSettings.TEST_SETTINGS); + private static Neo4jContainer neo4jContainer; + private static Driver driver; + private static boolean nginxRunning; + + static { + dockerAvailable = isDockerAvailable(); + instance = new DatabaseExtension(); + defaultConfig = new HashMap<>(); + defaultConfig.put(SSL_POLICY_BOLT_ENABLED, "true"); + defaultConfig.put(SSL_POLICY_BOLT_CLIENT_AUTH, "NONE"); + defaultConfig.put(BOLT_TLS_LEVEL, OPTIONAL.toString()); + + if (dockerAvailable) { + CertificateKeyPair pair = generateCertificateAndKey(); + cert = pair.cert(); + key = pair.key(); + + network = Network.newNetwork(); + neo4jContainer = setupNeo4jContainer(cert, key, defaultConfig); + neo4jContainer.start(); + nginx = setupNginxContainer(); + nginx.start(); + nginxRunning = true; + + URI neo4jBoltUri = URI.create(neo4jContainer.getBoltUrl()); + URI neo4jHttpUri = URI.create(neo4jContainer.getHttpUrl()); + + boltUri = URI.create(String.format( + "%s://%s:%d", neo4jBoltUri.getScheme(), neo4jBoltUri.getHost(), nginx.getMappedPort(BOLT_PORT))); + httpUri = URI.create(String.format( + "%s://%s:%d", neo4jHttpUri.getScheme(), neo4jHttpUri.getHost(), nginx.getMappedPort(HTTP_PORT))); + + authToken = AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword()); + driver = GraphDatabase.driver(boltUri, authToken); + waitForBoltAvailability(); + } else { + // stub init, this is not usable when Docker is unavailable + boltUri = URI.create(""); + httpUri = URI.create(""); + authToken = AuthTokens.none(); + cert = new File(""); + key = new File(""); + network = null; + nginx = new GenericContainer<>(DockerImageName.parse("alpine:latest")); + } } - public DatabaseExtension(Neo4jSettings settings) { - this.settings = settings; + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return dockerAvailable + ? ConditionEvaluationResult.enabled("Docker is available") + : ConditionEvaluationResult.disabled("Docker is unavailable"); } @Override public void beforeEach(ExtensionContext context) throws Exception { - runner = getOrCreateGlobalRunner(); - runner.ensureRunning(settings); - TestUtil.cleanDb(driver()); + TestUtil.cleanDb(driver); } @Override - public void afterAll(ExtensionContext context) { - if (runner != null) { - runner.stopNeo4j(); + public void afterEach(ExtensionContext context) throws Exception { + if (!nginxRunning) { + startProxy(); } } + @Override + public void afterAll(ExtensionContext context) { + deleteAndStartNeo4j(Collections.emptyMap()); + } + public Driver driver() { - return runner.driver(); + return driver; } public TypeSystem typeSystem() { - return driver().defaultTypeSystem(); + return driver.defaultTypeSystem(); } - public void forceRestartDb() { - runner.forceToRestart(); - } + public void deleteAndStartNeo4j(Map config) { + Map updatedConfig = new HashMap<>(defaultConfig); + updatedConfig.putAll(config); - public void restartDb(Neo4jSettings neo4jSettings) { - runner.restartNeo4j(neo4jSettings); + neo4jContainer.stop(); + neo4jContainer = setupNeo4jContainer(cert, key, updatedConfig); + neo4jContainer.start(); + if (REQUIRED.toString().equals(config.get(BOLT_TLS_LEVEL))) { + driver = GraphDatabase.driver( + boltUri, + authToken, + Config.builder() + .withTrustStrategy(Config.TrustStrategy.trustCustomCertificateSignedBy(cert)) + .withEncryption() + .build()); + } else { + driver = GraphDatabase.driver(boltUri, authToken); + } + waitForBoltAvailability(); } - public URL putTmpFile(String prefix, String suffix, String contents) throws IOException { + public String addImportFile(String prefix, String suffix, String contents) throws IOException { File tmpFile = File.createTempFile(prefix, suffix, null); tmpFile.deleteOnExit(); try (PrintWriter out = new PrintWriter(tmpFile)) { out.println(contents); } - return tmpFile.toURI().toURL(); + Path tmpFilePath = tmpFile.toPath(); + Path targetPath = + Paths.get("/var/lib/neo4j/import", tmpFilePath.getFileName().toString()); + neo4jContainer.copyFileToContainer(MountableFile.forHostPath(tmpFilePath), targetPath.toString()); + return String.format("file:///%s", tmpFile.getName()); } public URI uri() { - return runner.boltUri(); + return boltUri; } public int httpPort() { - return runner.httpPort(); + return httpUri.getPort(); } public int boltPort() { - return runner.boltPort(); + return boltUri.getPort(); } public AuthToken authToken() { - return DEFAULT_AUTH_TOKEN; + return authToken; + } + + public String adminPassword() { + return neo4jContainer.getAdminPassword(); } public BoltServerAddress address() { - return runner.boltAddress(); + return new BoltServerAddress(boltUri); } - public void updateEncryptionKeyAndCert(File key, File cert) throws Exception { - FileTools.copyFile(key, tlsKeyFile()); - FileTools.copyFile(cert, tlsCertFile()); - debug("Updated neo4j key and certificate file."); - runner.forceToRestart(); // needs to force to restart as no configuration changed + public void updateEncryptionKeyAndCert(File key, File cert) { + System.out.println("Updated neo4j key and certificate file."); + neo4jContainer.stop(); + neo4jContainer = setupNeo4jContainer(cert, key, defaultConfig); + neo4jContainer.start(); + driver = GraphDatabase.driver(boltUri, authToken); + waitForBoltAvailability(); } public File tlsCertFile() { - return new File(HOME_DIR, DEFAULT_TLS_CERT_PATH); + return cert; } - public File tlsKeyFile() { - return new File(HOME_DIR, DEFAULT_TLS_KEY_PATH); + public void startProxy() { + try { + nginx.execInContainer("nginx"); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + nginxRunning = true; } - public void ensureProcedures(String jarName) throws IOException { - // These procedures was written against 3.x API. - // As graph database service API is totally changed since 4.0. These procedures are no long valid. - assumeTrue(version().lessThan(ServerVersion.v4_0_0)); - File procedureJar = new File(HOME_DIR, "plugins/" + jarName); - if (!procedureJar.exists()) { - FileTools.copyFile(new File(TEST_RESOURCE_FOLDER_PATH, jarName), procedureJar); - debug("Added a new procedure `%s`", jarName); - runner.forceToRestart(); // needs to force to restart as no configuration changed + public void stopProxy() { + try { + nginx.execInContainer("nginx", "-s", "stop"); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); } + nginxRunning = false; } - public void startDb() { - runner.startNeo4j(); + public boolean isNeo4j44OrEarlier() { + return isNeo4jVersionOrEarlier(4, 4); } - public void stopDb() { - runner.stopNeo4j(); + public boolean isNeo4j43OrEarlier() { + return isNeo4jVersionOrEarlier(4, 3); + } + + private boolean isNeo4jVersionOrEarlier(int major, int minor) { + try (Session session = driver.session()) { + String neo4jVersion = session.readTransaction( + tx -> tx.run("CALL dbms.components() YIELD versions " + "RETURN versions[0] AS version") + .single() + .get("version") + .asString()); + String[] versions = neo4jVersion.split("\\."); + return parseInt(versions[0]) <= major && parseInt(versions[1]) <= minor; + } } - public ServerVersion version() { - return ServerVersion.version(driver()); + public static DatabaseExtension getInstance() { + return instance; } - public void dumpLogs() { - runner.dumpDebugLog(); + public static GeneralName getDockerHostGeneralName() { + String host = DockerClientFactory.instance().dockerHostIpAddress(); + GeneralName generalName; + try { + generalName = new GeneralName(GeneralName.iPAddress, host); + } catch (IllegalArgumentException e) { + generalName = new GeneralName(GeneralName.dNSName, host); + } + return generalName; + } + + private static CertificateKeyPair generateCertificateAndKey() { + try { + return CertificateUtil.createNewCertificateAndKey(getDockerHostGeneralName()); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + private static Neo4jContainer setupNeo4jContainer(File cert, File key, Map config) { + String neo4JVersion = + Optional.ofNullable(System.getenv("NEO4J_VERSION")).orElse("4.4"); + + ImageFromDockerfile extendedNeo4jImage = new ImageFromDockerfile() + .withDockerfileFromBuilder(builder -> builder.from(String.format("neo4j:%s-enterprise", neo4JVersion)) + .run("mkdir /var/lib/neo4j/certificates/bolt") + .copy("public.crt", "/var/lib/neo4j/certificates/bolt/") + .copy("private.key", "/var/lib/neo4j/certificates/bolt/") + .build()) + .withFileFromPath("public.crt", cert.toPath()) + .withFileFromPath("private.key", key.toPath()); + + DockerImageName extendedNeo4jImageAsSubstitute = + DockerImageName.parse(extendedNeo4jImage.get()).asCompatibleSubstituteFor("neo4j"); + + neo4jContainer = new Neo4jContainer<>(extendedNeo4jImageAsSubstitute) + .withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes") + .withNetwork(network) + .withNetworkAliases("neo4j"); + for (Map.Entry entry : config.entrySet()) { + neo4jContainer.withNeo4jConfig(entry.getKey(), entry.getValue()); + } + + return neo4jContainer; + } + + private static GenericContainer setupNginxContainer() { + ImageFromDockerfile extendedNginxImage = new ImageFromDockerfile() + .withDockerfileFromBuilder(builder -> builder.from("nginx:1.23.0-alpine") + .copy("nginx.conf", "/etc/nginx/") + .build()) + .withFileFromClasspath("nginx.conf", "nginx.conf"); + + //noinspection rawtypes + return new GenericContainer(extendedNginxImage.get()) + .withNetwork(network) + .withExposedPorts(BOLT_PORT, HTTP_PORT) + .withCommand("sh", "-c", "nginx && while sleep 3600; do :; done"); + } + + private static void waitForBoltAvailability() { + int maxAttempts = 600; + for (int attempt = 0; attempt < maxAttempts; attempt++) { + try { + driver.verifyConnectivity(); + return; + } catch (RuntimeException verificationException) { + if (attempt == maxAttempts - 1) { + throw new RuntimeException( + "Timed out waiting for Neo4j to become available over Bolt", verificationException); + } + try { + Thread.sleep(500); + } catch (InterruptedException interruptedException) { + interruptedException.addSuppressed(verificationException); + throw new RuntimeException( + "Interrupted while waiting for Neo4j to become available over Bolt", interruptedException); + } + } + } + } + + private static boolean isDockerAvailable() { + try { + DockerClientFactory.instance().client(); + return true; + } catch (Throwable ex) { + return false; + } } } diff --git a/driver/src/test/java/org/neo4j/driver/util/Neo4jRunner.java b/driver/src/test/java/org/neo4j/driver/util/Neo4jRunner.java deleted file mode 100644 index 5116ad243d..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/Neo4jRunner.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util; - -import static java.util.Arrays.asList; -import static java.util.logging.Level.INFO; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.neo4j.driver.AuthTokens.basic; -import static org.neo4j.driver.Logging.console; -import static org.neo4j.driver.util.FileTools.moveFile; -import static org.neo4j.driver.util.FileTools.updateProperties; -import static org.neo4j.driver.util.Neo4jSettings.CURRENT_BOLT_PORT; -import static org.neo4j.driver.util.Neo4jSettings.CURRENT_HTTP_PORT; -import static org.neo4j.driver.util.Neo4jSettings.TEST_JVM_ID; -import static org.neo4j.driver.util.cc.CommandLineUtil.boltKitAvailable; -import static org.neo4j.driver.util.cc.CommandLineUtil.executeCommand; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.StandardSocketOptions; -import java.net.URI; -import java.nio.channels.SocketChannel; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import org.neo4j.driver.AuthToken; -import org.neo4j.driver.Config; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.util.ErrorUtil; - -/** - * This class wraps the neo4j stand-alone jar in some code to help pulling it in from a remote URL and then launching - * it in a separate process. - */ -public class Neo4jRunner { - private static Neo4jRunner globalInstance; - - private static final String DEFAULT_NEOCTRL_ARGS = "-e 3.5.11"; - private static final String ENV_NEOCTRL_ARGS = System.getenv("JAVA_DRIVER_NEOCTRL_ARGS"); - public static final String NEOCTRL_ARGS = - System.getProperty("neoctrl.args", ENV_NEOCTRL_ARGS == null ? DEFAULT_NEOCTRL_ARGS : ENV_NEOCTRL_ARGS); - public static final Config DEFAULT_CONFIG = - Config.builder().withLogging(console(INFO)).withoutEncryption().build(); - - public static final String USER = "neo4j"; - public static final String PASSWORD = "password"; - public static final AuthToken DEFAULT_AUTH_TOKEN = basic(USER, PASSWORD); - - private Neo4jSettings currentSettings = Neo4jSettings.TEST_SETTINGS; - - public static final String TARGET_DIR = new File("../target").getAbsolutePath(); - private static final String NEO4J_DIR = new File(TARGET_DIR, "test-server-" + TEST_JVM_ID).getAbsolutePath(); - public static final String HOME_DIR = new File(NEO4J_DIR, "neo4jHome").getAbsolutePath(); - - private Driver driver; - private boolean restartDriver; - - public int httpPort() { - return CURRENT_HTTP_PORT; - } - - public int boltPort() { - return CURRENT_BOLT_PORT; - } - - public BoltServerAddress boltAddress() { - return new BoltServerAddress(boltUri()); - } - - public URI boltUri() { - return URI.create("bolt://localhost:" + boltPort()); - } - - /** Global runner controlling a single server, used to avoid having to restart the server between tests */ - public static synchronized Neo4jRunner getOrCreateGlobalRunner() throws IOException { - assumeTrue(boltKitAvailable(), "BoltKit support unavailable"); - if (globalInstance == null) { - globalInstance = new Neo4jRunner(); - } - return globalInstance; - } - - public static synchronized boolean globalRunnerExists() { - return globalInstance != null; - } - - private Neo4jRunner() throws IOException { - try { - installNeo4j(); - updateServerSettingsFile(); - try { - startNeo4j(); - } catch (Exception e) { - debug("Failed to start server first time due to error: " - + ErrorUtil.getRootCause(e).getMessage()); - debug("Retry to start server again."); - startNeo4j(); - } - } finally { - // Make sure we stop on JVM exit even if start failed - installShutdownHook(); - } - } - - public void ensureRunning(Neo4jSettings neo4jSettings) { - ServerStatus status = serverStatus(); - switch (status) { - case OFFLINE: - updateServerSettings(neo4jSettings); - startNeo4j(); - break; - case ONLINE: - restartNeo4j(neo4jSettings); - break; - } - } - - public Driver driver() { - if (restartDriver) { - restartDriver = false; - if (driver != null) { - driver.close(); - driver = null; - } - } - - if (driver == null) { - driver = GraphDatabase.driver(boltUri(), DEFAULT_AUTH_TOKEN, DEFAULT_CONFIG); - } - return driver; - } - - private void installNeo4j() throws IOException { - File targetHomeFile = new File(HOME_DIR); - if (targetHomeFile.exists()) { - debug("Found and using server installed at `%s`. ", HOME_DIR); - } else { - List commands = new ArrayList<>(); - commands.add("neoctrl-install"); - String[] split = NEOCTRL_ARGS.trim().split("\\s+"); - commands.addAll(asList(split)); - commands.add(NEO4J_DIR); - - String tempHomeDir = executeCommand(commands).trim(); - debug("Downloaded server at `%s`, now renaming to `%s`.", tempHomeDir, HOME_DIR); - - moveFile(new File(tempHomeDir), targetHomeFile); - debug("Installed server at `%s`.", HOME_DIR); - executeCommand("neoctrl-create-user", HOME_DIR, USER, PASSWORD); - } - } - - public void startNeo4j() { - debug("Starting server..."); - executeCommand("neoctrl-start", HOME_DIR, "-v"); - debug("Server started."); - } - - public synchronized void stopNeo4j() { - if (serverStatus() == ServerStatus.OFFLINE) { - return; - } - restartDriver = true; - - debug("Stopping server..."); - executeCommand("neoctrl-stop", HOME_DIR); - debug("Server stopped."); - } - - public void killNeo4j() { - if (serverStatus() == ServerStatus.OFFLINE) { - return; - } - restartDriver = true; - - debug("Killing server..."); - executeCommand("neoctrl-stop", "-k", HOME_DIR); - debug("Server killed."); - } - - public void forceToRestart() { - stopNeo4j(); - startNeo4j(); - } - - /** - * Restart the server with default testing server configuration - */ - public void restartNeo4j() { - restartNeo4j(Neo4jSettings.TEST_SETTINGS); - } - - /** - * Will only restart the server if any configuration changes happens - * @param neo4jSettings - */ - public void restartNeo4j(Neo4jSettings neo4jSettings) { - if (updateServerSettings(neo4jSettings)) // needs to update server setting files - { - forceToRestart(); - } - } - - /** - * prints the debug log contents to stdOut - */ - public void dumpDebugLog() { - try { - System.out.println("Debug log for: " + HOME_DIR); - Scanner input = new Scanner(new File(HOME_DIR + "/logs/debug.log")); - - while (input.hasNextLine()) { - System.out.println(input.nextLine()); - } - } catch (FileNotFoundException e) { - System.out.println("Unable to find debug log file for: " + HOME_DIR); - e.printStackTrace(); - } - } - - private enum ServerStatus { - ONLINE, - OFFLINE - } - - private ServerStatus serverStatus() { - try { - SocketChannel soChannel = SocketChannel.open(); - soChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); - BoltServerAddress address = boltAddress(); - soChannel.connect(new InetSocketAddress(address.connectionHost(), address.port())); - soChannel.close(); - return ServerStatus.ONLINE; - } catch (IOException e) { - return ServerStatus.OFFLINE; - } - } - - private boolean updateServerSettings(Neo4jSettings newSetting) { - if (currentSettings.equals(newSetting)) { - return false; - } else { - currentSettings = newSetting; - } - updateServerSettingsFile(); - return true; - } - - /** - * Write updated neo4j settings into neo4j-server.properties for use by the next start - */ - private void updateServerSettingsFile() { - Map propertiesMap = currentSettings.propertiesMap(); - - if (propertiesMap.isEmpty()) { - return; - } - - File oldFile = new File(HOME_DIR, "conf/neo4j.conf"); - try { - debug("Changing server properties file (for next start): %s", oldFile.getCanonicalPath()); - for (Map.Entry property : propertiesMap.entrySet()) { - String name = property.getKey(); - Object value = property.getValue(); - debug("%s=%s", name, value); - } - - updateProperties(oldFile, propertiesMap, currentSettings.excludes()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void installShutdownHook() { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - debug("Starting shutdown hook"); - if (driver != null) { - driver.close(); - } - stopNeo4j(); - debug("Finished shutdown hook"); - } catch (Exception e) { - e.printStackTrace(); - } - })); - } - - public static void debug(String text, Object... args) { - System.out.println(String.format(text, args)); - } - - public static void debug(String text) { - System.out.println(text); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/Neo4jSettings.java b/driver/src/test/java/org/neo4j/driver/util/Neo4jSettings.java index 20c7fa1d3a..fcf7d063b4 100644 --- a/driver/src/test/java/org/neo4j/driver/util/Neo4jSettings.java +++ b/driver/src/test/java/org/neo4j/driver/util/Neo4jSettings.java @@ -29,27 +29,33 @@ public class Neo4jSettings { public static final String DATA_DIR = "dbms.directories.data"; public static final String IMPORT_DIR = "dbms.directories.import"; - public static final String LISTEN_ADDR = "dbms.connectors.default_listen_address"; + public static final String SSL_POLICY_BOLT_ENABLED = "dbms.ssl.policy.bolt.enabled"; + public static final String SSL_POLICY_BOLT_CLIENT_AUTH = "dbms.ssl.policy.bolt.client_auth"; + // 5.0 + public static final String LISTEN_ADDR = "dbms.default_listen_address"; public static final String IPV6_ENABLED_ADDR = "::"; public static final String BOLT_TLS_LEVEL = "dbms.connector.bolt.tls_level"; private static final String DEFAULT_IMPORT_DIR = "import"; - private static final String DEFAULT_CERT_DIR = "certificates"; - public static final String DEFAULT_TLS_CERT_PATH = DEFAULT_CERT_DIR + "/neo4j.cert"; - public static final String DEFAULT_TLS_KEY_PATH = DEFAULT_CERT_DIR + "/neo4j.key"; public static final String DEFAULT_BOLT_TLS_LEVEL = BoltTlsLevel.OPTIONAL.toString(); public static final String DEFAULT_DATA_DIR = "data"; static final int TEST_JVM_ID = Integer.getInteger("testJvmId", 0); - private static final int DEFAULT_HTTP_PORT = 7000; - private static final int DEFAULT_HTTPS_PORT = 8000; - private static final int DEFAULT_BOLT_PORT = 9000; + private static final int DEFAULT_HTTP_PORT = 12000; + private static final int DEFAULT_HTTPS_PORT = 13000; + private static final int DEFAULT_BOLT_PORT = 14000; + private static final int DEFAULT_DISCOVERY_LISTEN_PORT = 15000; + private static final int DEFAULT_RAFT_ADVERTISED_PORT = 16000; + private static final int DEFAULT_TX_LISTEN_PORT = 17000; static final int CURRENT_HTTP_PORT = DEFAULT_HTTP_PORT + TEST_JVM_ID; private static final int CURRENT_HTTPS_PORT = DEFAULT_HTTPS_PORT + TEST_JVM_ID; static final int CURRENT_BOLT_PORT = DEFAULT_BOLT_PORT + TEST_JVM_ID; + static final int CURRENT_DISCOVERY_LISTEN_PORT = DEFAULT_DISCOVERY_LISTEN_PORT + TEST_JVM_ID; + static final int CURRENT_RAFT_ADVERTISED_PORT = DEFAULT_RAFT_ADVERTISED_PORT + TEST_JVM_ID; + static final int CURRENT_TX_LISTEN_PORT = DEFAULT_TX_LISTEN_PORT + TEST_JVM_ID; private static final String WINDOWS_SERVICE_NAME = "neo4j-" + TEST_JVM_ID; @@ -64,6 +70,20 @@ public class Neo4jSettings { ":" + CURRENT_HTTPS_PORT, "dbms.connector.bolt.listen_address", ":" + CURRENT_BOLT_PORT, + "dbms.cluster.discovery.initial_members", + "localhost:" + CURRENT_DISCOVERY_LISTEN_PORT, + "server.discovery.listen_address", + ":" + CURRENT_DISCOVERY_LISTEN_PORT, + "cluster.raft_advertised_address", + ":" + CURRENT_RAFT_ADVERTISED_PORT, + "cluster.raft_listen_address", + ":" + CURRENT_RAFT_ADVERTISED_PORT, + "cluster.transaction_listen_address", + ":" + CURRENT_TX_LISTEN_PORT, + "cluster.transaction_advertised_address", + ":" + CURRENT_TX_LISTEN_PORT, + "server.cluster.advertised_address", + ":" + CURRENT_TX_LISTEN_PORT, "dbms.windows_service_name", WINDOWS_SERVICE_NAME, DATA_DIR, @@ -74,7 +94,7 @@ public class Neo4jSettings { DEFAULT_BOLT_TLS_LEVEL, LISTEN_ADDR, IPV6_ENABLED_ADDR), - Collections.emptySet()); + Collections.emptySet()); public enum BoltTlsLevel { OPTIONAL, @@ -91,21 +111,6 @@ public Map propertiesMap() { return settings; } - public Neo4jSettings updateWith(String key, String value) { - return updateWith(map(key, value), excludes); - } - - private Neo4jSettings updateWith(Map updates, Set excludes) { - HashMap newSettings = new HashMap<>(settings); - for (Map.Entry entry : updates.entrySet()) { - newSettings.put(entry.getKey(), entry.getValue()); - } - for (String exclude : excludes) { - newSettings.remove(exclude); - } - return new Neo4jSettings(newSettings, excludes); - } - public Neo4jSettings without(String key) { Set newExcludes = new HashSet<>(excludes); newExcludes.add(key); diff --git a/driver/src/test/java/org/neo4j/driver/util/ProcessEnvConfigurator.java b/driver/src/test/java/org/neo4j/driver/util/ProcessEnvConfigurator.java deleted file mode 100644 index 7c60e7e02c..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/ProcessEnvConfigurator.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util; - -public final class ProcessEnvConfigurator { - /** - * Name of environment variable used by the Neo4j database. - */ - private static final String JAVA_HOME = "JAVA_HOME"; - /** - * Name of environment variable to be used for the Neo4j database, defined by the build system. - */ - private static final String NEO4J_JAVA = "NEO4J_JAVA"; - /** - * Name of an optional environment variable containing file path to a local Neo4j package. - * This package is used by boltkit instead of downloading a package with the specified Neo4j version. - */ - private static final String BOLTKIT_LOCAL_PACKAGE = "NEOCTRL_LOCAL_PACKAGE"; - - private ProcessEnvConfigurator() {} - - public static void configure(ProcessBuilder processBuilder) { - processBuilder.environment().put(JAVA_HOME, determineJavaHome()); - - String localPackage = determineLocalPackage(); - if (localPackage != null) { - processBuilder.environment().put(BOLTKIT_LOCAL_PACKAGE, localPackage); - } - } - - /** - * This driver is built to work with multiple java versions. Neo4j, however, works with a specific version of - * Java. This allows specifying which Java version to use for Neo4j separately from which version to use for - * the driver tests. - *

- * This method determines which java home to use based on present environment variables. - * - * @return path to the java home. - */ - private static String determineJavaHome() { - return System.getenv().getOrDefault(NEO4J_JAVA, System.getProperties().getProperty("java.home")); - } - - private static String determineLocalPackage() { - String value = System.getenv().getOrDefault(BOLTKIT_LOCAL_PACKAGE, "").trim(); - return value.isEmpty() ? null : value; - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/CommandLineException.java b/driver/src/test/java/org/neo4j/driver/util/cc/CommandLineException.java deleted file mode 100644 index d245aaaee0..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/CommandLineException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -class CommandLineException extends RuntimeException { - CommandLineException(String message) { - super(message); - } - - CommandLineException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/CommandLineUtil.java b/driver/src/test/java/org/neo4j/driver/util/cc/CommandLineUtil.java deleted file mode 100644 index b6027238ad..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/CommandLineUtil.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.util.cc; - -import static java.lang.System.lineSeparator; -import static java.util.Arrays.asList; -import static java.util.concurrent.TimeUnit.MINUTES; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import org.neo4j.driver.util.DaemonThreadFactory; -import org.neo4j.driver.util.ProcessEnvConfigurator; - -public class CommandLineUtil { - private static final ExecutorService executor = - Executors.newCachedThreadPool(new DaemonThreadFactory("command-line-thread-")); - - public static boolean boltKitAvailable() { - try { - executeCommand("neoctrl-cluster", "--help"); - return true; - } catch (CommandLineException e) { - return false; - } - } - - public static String executeCommand(List commands) { - try { - ProcessBuilder processBuilder = new ProcessBuilder().command(commands); - ProcessEnvConfigurator.configure(processBuilder); - return executeAndGetStdOut(processBuilder); - } catch (IOException | CommandLineException e) { - throw new CommandLineException("Error running command " + commands, e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new CommandLineException("Interrupted while waiting for command " + commands, e); - } - } - - public static String executeCommand(String... command) { - return executeCommand(asList(command)); - } - - private static String executeAndGetStdOut(ProcessBuilder processBuilder) throws IOException, InterruptedException { - Process process = processBuilder.start(); - Future stdOutFuture = read(process.getInputStream()); - Future stdErrFuture = read(process.getErrorStream()); - int exitCode = process.waitFor(); - String stdOut = get(stdOutFuture); - String stdErr = get(stdErrFuture); - if (exitCode != 0) { - throw new CommandLineException("Non-zero exit code\nSTDOUT:\n" + stdOut + "\nSTDERR:\n" + stdErr); - } - return stdOut; - } - - private static Future read(final InputStream input) { - return executor.submit(new Callable() { - @Override - public String call() throws Exception { - return readToString(input); - } - }); - } - - private static String readToString(InputStream input) { - StringBuilder result = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) { - String line; - while ((line = reader.readLine()) != null) { - result.append(line).append(lineSeparator()); - } - } catch (IOException e) { - throw new CommandLineException("Unable to read from stream", e); - } - return result.toString(); - } - - private static T get(Future future) { - try { - return future.get(10, MINUTES); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/driver/src/test/resources/logback-test.xml b/driver/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..ea9a2a3fac --- /dev/null +++ b/driver/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/driver/src/test/resources/nginx.conf b/driver/src/test/resources/nginx.conf new file mode 100644 index 0000000000..4b8694ed11 --- /dev/null +++ b/driver/src/test/resources/nginx.conf @@ -0,0 +1,30 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + +stream { + server { + resolver 127.0.0.11 ipv6=off; + + set $upstream neo4j:7687; + + listen 7687; + proxy_pass $upstream; + } + + server { + resolver 127.0.0.11 ipv6=off; + + set $upstream neo4j:7474; + + listen 7474; + proxy_pass $upstream; + } +} \ No newline at end of file diff --git a/examples/pom.xml b/examples/pom.xml index 0524e43b04..a3d5ace7cf 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -74,6 +74,10 @@ neo4j test + + org.bouncycastle + bcpkix-jdk15on + diff --git a/examples/src/test/java/org/neo4j/docs/driver/ExamplesIT.java b/examples/src/test/java/org/neo4j/docs/driver/ExamplesIT.java index b92fce2084..e62d97a2da 100644 --- a/examples/src/test/java/org/neo4j/docs/driver/ExamplesIT.java +++ b/examples/src/test/java/org/neo4j/docs/driver/ExamplesIT.java @@ -36,8 +36,6 @@ import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.util.Neo4jEdition.ENTERPRISE; import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V4; -import static org.neo4j.driver.util.Neo4jRunner.PASSWORD; -import static org.neo4j.driver.util.Neo4jRunner.USER; import static org.neo4j.driver.util.TestUtil.await; import static org.neo4j.driver.util.TestUtil.createDatabase; import static org.neo4j.driver.util.TestUtil.dropDatabase; @@ -52,8 +50,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.Session; @@ -70,8 +66,9 @@ import org.neo4j.driver.util.TestUtil; @ParallelizableIT -@Execution(ExecutionMode.CONCURRENT) class ExamplesIT { + static final String USER = "neo4j"; + @RegisterExtension static final DatabaseExtension neo4j = new DatabaseExtension(); @@ -128,7 +125,8 @@ void setUp() { @Test void testShouldRunAutocommitTransactionExample() throws Exception { // Given - try (AutocommitTransactionExample example = new AutocommitTransactionExample(uri, USER, PASSWORD)) { + try (AutocommitTransactionExample example = + new AutocommitTransactionExample(uri, USER, neo4j.adminPassword())) { // When example.addPerson("Alice"); @@ -139,7 +137,8 @@ void testShouldRunAutocommitTransactionExample() throws Exception { @Test void testShouldRunAsyncAutocommitTransactionExample() throws Exception { - try (AsyncAutocommitTransactionExample example = new AsyncAutocommitTransactionExample(uri, USER, PASSWORD)) { + try (AsyncAutocommitTransactionExample example = + new AsyncAutocommitTransactionExample(uri, USER, neo4j.adminPassword())) { // create some 'Product' nodes try (Session session = neo4j.driver().session()) { session.run("UNWIND ['Tesseract', 'Orb', 'Eye of Agamotto'] AS item " @@ -157,7 +156,7 @@ void testShouldAsyncRunResultConsumeExample() throws Exception { // Given write("CREATE (a:Person {name: 'Alice'})"); write("CREATE (a:Person {name: 'Bob'})"); - try (AsyncResultConsumeExample example = new AsyncResultConsumeExample(uri, USER, PASSWORD)) { + try (AsyncResultConsumeExample example = new AsyncResultConsumeExample(uri, USER, neo4j.adminPassword())) { // When List names = await(example.getPeople()); @@ -171,7 +170,8 @@ void testShouldAsyncRunMultipleTransactionExample() throws Exception { // Given write("CREATE (a:Person {name: 'Alice'})"); write("CREATE (a:Person {name: 'Bob'})"); - try (AsyncRunMultipleTransactionExample example = new AsyncRunMultipleTransactionExample(uri, USER, PASSWORD)) { + try (AsyncRunMultipleTransactionExample example = + new AsyncRunMultipleTransactionExample(uri, USER, neo4j.adminPassword())) { // When Integer nodesCreated = await(example.addEmployees("Acme")); @@ -186,7 +186,7 @@ void testShouldAsyncRunMultipleTransactionExample() throws Exception { @Test void testShouldRunConfigConnectionPoolExample() throws Exception { // Given - try (ConfigConnectionPoolExample example = new ConfigConnectionPoolExample(uri, USER, PASSWORD)) { + try (ConfigConnectionPoolExample example = new ConfigConnectionPoolExample(uri, USER, neo4j.adminPassword())) { // Then assertTrue(example.canConnect()); } @@ -195,7 +195,7 @@ void testShouldRunConfigConnectionPoolExample() throws Exception { @Test void testShouldRunBasicAuthExample() throws Exception { // Given - try (BasicAuthExample example = new BasicAuthExample(uri, USER, PASSWORD)) { + try (BasicAuthExample example = new BasicAuthExample(uri, USER, neo4j.adminPassword())) { // Then assertTrue(example.canConnect()); } @@ -204,7 +204,8 @@ void testShouldRunBasicAuthExample() throws Exception { @Test void testShouldRunConfigConnectionTimeoutExample() throws Exception { // Given - try (ConfigConnectionTimeoutExample example = new ConfigConnectionTimeoutExample(uri, USER, PASSWORD)) { + try (ConfigConnectionTimeoutExample example = + new ConfigConnectionTimeoutExample(uri, USER, neo4j.adminPassword())) { // Then assertThat(example, instanceOf(ConfigConnectionTimeoutExample.class)); } @@ -213,7 +214,7 @@ void testShouldRunConfigConnectionTimeoutExample() throws Exception { @Test void testShouldRunConfigMaxRetryTimeExample() throws Exception { // Given - try (ConfigMaxRetryTimeExample example = new ConfigMaxRetryTimeExample(uri, USER, PASSWORD)) { + try (ConfigMaxRetryTimeExample example = new ConfigMaxRetryTimeExample(uri, USER, neo4j.adminPassword())) { // Then assertThat(example, instanceOf(ConfigMaxRetryTimeExample.class)); } @@ -222,7 +223,7 @@ void testShouldRunConfigMaxRetryTimeExample() throws Exception { @Test void testShouldRunConfigTrustExample() throws Exception { // Given - try (ConfigTrustExample example = new ConfigTrustExample(uri, USER, PASSWORD)) { + try (ConfigTrustExample example = new ConfigTrustExample(uri, USER, neo4j.adminPassword())) { // Then assertThat(example, instanceOf(ConfigTrustExample.class)); } @@ -231,7 +232,7 @@ void testShouldRunConfigTrustExample() throws Exception { @Test void testShouldRunConfigUnencryptedExample() throws Exception { // Given - try (ConfigUnencryptedExample example = new ConfigUnencryptedExample(uri, USER, PASSWORD)) { + try (ConfigUnencryptedExample example = new ConfigUnencryptedExample(uri, USER, neo4j.adminPassword())) { // Then assertThat(example, instanceOf(ConfigUnencryptedExample.class)); } @@ -240,7 +241,7 @@ void testShouldRunConfigUnencryptedExample() throws Exception { @Test void testShouldRunCypherErrorExample() throws Exception { // Given - try (CypherErrorExample example = new CypherErrorExample(uri, USER, PASSWORD)) { + try (CypherErrorExample example = new CypherErrorExample(uri, USER, neo4j.adminPassword())) { // When & Then StdIOCapture stdIO = new StdIOCapture(); try (AutoCloseable ignored = stdIO.capture()) { @@ -255,7 +256,7 @@ void testShouldRunCypherErrorExample() throws Exception { @Test void testShouldRunDriverLifecycleExample() throws Exception { // Given - try (DriverLifecycleExample example = new DriverLifecycleExample(uri, USER, PASSWORD)) { + try (DriverLifecycleExample example = new DriverLifecycleExample(uri, USER, neo4j.adminPassword())) { // Then assertThat(example, instanceOf(DriverLifecycleExample.class)); } @@ -264,7 +265,7 @@ void testShouldRunDriverLifecycleExample() throws Exception { @Test void testShouldRunHelloWorld() throws Exception { // Given - try (HelloWorldExample greeter = new HelloWorldExample(uri, USER, PASSWORD)) { + try (HelloWorldExample greeter = new HelloWorldExample(uri, USER, neo4j.adminPassword())) { // When StdIOCapture stdIO = new StdIOCapture(); @@ -285,7 +286,8 @@ void testShouldRunDriverIntroduction() throws Exception { .withEncryption() .withTrustStrategy(trustAllCertificates()) .build(); - try (DriverIntroductionExample intro = new DriverIntroductionExample(uri, USER, PASSWORD, config)) { + try (DriverIntroductionExample intro = + new DriverIntroductionExample(uri, USER, neo4j.adminPassword(), config)) { // When StdIOCapture stdIO = new StdIOCapture(); @@ -304,7 +306,7 @@ void testShouldRunDriverIntroduction() throws Exception { @Test void testShouldRunReadWriteTransactionExample() throws Exception { // Given - try (ReadWriteTransactionExample example = new ReadWriteTransactionExample(uri, USER, PASSWORD)) { + try (ReadWriteTransactionExample example = new ReadWriteTransactionExample(uri, USER, neo4j.adminPassword())) { // When long nodeID = example.addPerson("Alice"); @@ -318,7 +320,7 @@ void testShouldRunResultConsumeExample() throws Exception { // Given write("CREATE (a:Person {name: 'Alice'})"); write("CREATE (a:Person {name: 'Bob'})"); - try (ResultConsumeExample example = new ResultConsumeExample(uri, USER, PASSWORD)) { + try (ResultConsumeExample example = new ResultConsumeExample(uri, USER, neo4j.adminPassword())) { // When List names = example.getPeople(); @@ -332,7 +334,7 @@ void testShouldRunResultRetainExample() throws Exception { // Given write("CREATE (a:Person {name: 'Alice'})"); write("CREATE (a:Person {name: 'Bob'})"); - try (ResultRetainExample example = new ResultRetainExample(uri, USER, PASSWORD)) { + try (ResultRetainExample example = new ResultRetainExample(uri, USER, neo4j.adminPassword())) { // When example.addEmployees("Acme"); @@ -346,15 +348,15 @@ void testShouldRunResultRetainExample() throws Exception { @Test void testShouldRunServiceUnavailableExample() throws Exception { // Given - try (ServiceUnavailableExample example = new ServiceUnavailableExample(uri, USER, PASSWORD)) { + try (ServiceUnavailableExample example = new ServiceUnavailableExample(uri, USER, neo4j.adminPassword())) { try { // When - neo4j.stopDb(); + neo4j.stopProxy(); // Then assertThat(example.addItem(), equalTo(false)); } finally { - neo4j.startDb(); + neo4j.startProxy(); } } } @@ -362,7 +364,7 @@ void testShouldRunServiceUnavailableExample() throws Exception { @Test void testShouldRunSessionExample() throws Exception { // Given - try (SessionExample example = new SessionExample(uri, USER, PASSWORD)) { + try (SessionExample example = new SessionExample(uri, USER, neo4j.adminPassword())) { // When example.addPerson("Alice"); @@ -375,7 +377,7 @@ void testShouldRunSessionExample() throws Exception { @Test void testShouldRunTransactionFunctionExample() throws Exception { // Given - try (TransactionFunctionExample example = new TransactionFunctionExample(uri, USER, PASSWORD)) { + try (TransactionFunctionExample example = new TransactionFunctionExample(uri, USER, neo4j.adminPassword())) { // When example.addPerson("Alice"); @@ -387,7 +389,8 @@ void testShouldRunTransactionFunctionExample() throws Exception { @Test void testShouldConfigureTransactionTimeoutExample() throws Exception { // Given - try (TransactionTimeoutConfigExample example = new TransactionTimeoutConfigExample(uri, USER, PASSWORD)) { + try (TransactionTimeoutConfigExample example = + new TransactionTimeoutConfigExample(uri, USER, neo4j.adminPassword())) { // When example.addPerson("Alice"); @@ -399,7 +402,8 @@ void testShouldConfigureTransactionTimeoutExample() throws Exception { @Test void testShouldConfigureTransactionMetadataExample() throws Exception { // Given - try (TransactionMetadataConfigExample example = new TransactionMetadataConfigExample(uri, USER, PASSWORD)) { + try (TransactionMetadataConfigExample example = + new TransactionMetadataConfigExample(uri, USER, neo4j.adminPassword())) { // When example.addPerson("Alice"); @@ -410,7 +414,8 @@ void testShouldConfigureTransactionMetadataExample() throws Exception { @Test void testShouldRunAsyncTransactionFunctionExample() throws Exception { - try (AsyncTransactionFunctionExample example = new AsyncTransactionFunctionExample(uri, USER, PASSWORD)) { + try (AsyncTransactionFunctionExample example = + new AsyncTransactionFunctionExample(uri, USER, neo4j.adminPassword())) { // create some 'Product' nodes try (Session session = neo4j.driver().session()) { session.run( @@ -432,7 +437,7 @@ void testShouldRunAsyncTransactionFunctionExample() throws Exception { @Test void testPassBookmarksExample() throws Exception { - try (PassBookmarkExample example = new PassBookmarkExample(uri, USER, PASSWORD)) { + try (PassBookmarkExample example = new PassBookmarkExample(uri, USER, neo4j.adminPassword())) { // When example.addEmployAndMakeFriends(); @@ -458,7 +463,8 @@ void testPassBookmarksExample() throws Exception { @Test void testAsyncUnmanagedTransactionExample() throws Exception { - try (AsyncUnmanagedTransactionExample example = new AsyncUnmanagedTransactionExample(uri, USER, PASSWORD)) { + try (AsyncUnmanagedTransactionExample example = + new AsyncUnmanagedTransactionExample(uri, USER, neo4j.adminPassword())) { // create a 'Product' node try (Session session = neo4j.driver().session()) { session.run("CREATE (:Product {id: 0, title: 'Mind Gem'})"); @@ -491,7 +497,7 @@ void testSlf4jLogging() throws Exception { assertThat(logFileContent, is(emptyString())); String randomString = UUID.randomUUID().toString(); - try (Slf4jLoggingExample example = new Slf4jLoggingExample(uri, USER, PASSWORD)) { + try (Slf4jLoggingExample example = new Slf4jLoggingExample(uri, USER, neo4j.adminPassword())) { Object result = example.runReturnQuery(randomString); assertEquals(randomString, result); } @@ -505,7 +511,7 @@ void testSlf4jLogging() throws Exception { @Test void testHostnameVerificationExample() { try (HostnameVerificationExample example = - new HostnameVerificationExample(uri, USER, PASSWORD, neo4j.tlsCertFile())) { + new HostnameVerificationExample(uri, USER, neo4j.adminPassword(), neo4j.tlsCertFile())) { assertTrue(example.canConnect()); } } @@ -513,7 +519,8 @@ void testHostnameVerificationExample() { @Test @EnabledOnNeo4jWith(BOLT_V4) void testShouldRunRxAutocommitTransactionExample() throws Exception { - try (RxAutocommitTransactionExample example = new RxAutocommitTransactionExample(uri, USER, PASSWORD)) { + try (RxAutocommitTransactionExample example = + new RxAutocommitTransactionExample(uri, USER, neo4j.adminPassword())) { // create some 'Product' nodes try (Session session = neo4j.driver().session()) { session.run("UNWIND ['Tesseract', 'Orb', 'Eye of Agamotto'] AS item " @@ -532,7 +539,8 @@ void testShouldRunRxAutocommitTransactionExample() throws Exception { @Test @EnabledOnNeo4jWith(BOLT_V4) void testRxUnmanagedTransactionExample() throws Exception { - try (RxUnmanagedTransactionExample example = new RxUnmanagedTransactionExample(uri, USER, PASSWORD)) { + try (RxUnmanagedTransactionExample example = + new RxUnmanagedTransactionExample(uri, USER, neo4j.adminPassword())) { // create a 'Product' node try (Session session = neo4j.driver().session()) { session.run("CREATE (:Product {id: 0, title: 'Mind Gem'})"); @@ -551,7 +559,8 @@ void testRxUnmanagedTransactionExample() throws Exception { @Test @EnabledOnNeo4jWith(BOLT_V4) void testShouldRunRxTransactionFunctionExampleReactor() throws Exception { - try (RxTransactionFunctionExample example = new RxTransactionFunctionExample(uri, USER, PASSWORD)) { + try (RxTransactionFunctionExample example = + new RxTransactionFunctionExample(uri, USER, neo4j.adminPassword())) { // create some 'Product' nodes try (Session session = neo4j.driver().session()) { session.run( @@ -576,7 +585,8 @@ void testShouldRunRxTransactionFunctionExampleReactor() throws Exception { @Test @EnabledOnNeo4jWith(BOLT_V4) void testShouldRunRxTransactionFunctionExampleRxJava() throws Exception { - try (RxTransactionFunctionExample example = new RxTransactionFunctionExample(uri, USER, PASSWORD)) { + try (RxTransactionFunctionExample example = + new RxTransactionFunctionExample(uri, USER, neo4j.adminPassword())) { // create some 'Product' nodes try (Session session = neo4j.driver().session()) { session.run( @@ -604,7 +614,7 @@ void testShouldRunRxResultConsumeExampleReactor() throws Exception { // Given write("CREATE (a:Person {name: 'Alice'})"); write("CREATE (a:Person {name: 'Bob'})"); - try (RxResultConsumeExample example = new RxResultConsumeExample(uri, USER, PASSWORD)) { + try (RxResultConsumeExample example = new RxResultConsumeExample(uri, USER, neo4j.adminPassword())) { // When List names = await(example.getPeople()); @@ -619,7 +629,7 @@ void testShouldRunRxResultConsumeExampleRxJava() throws Exception { // Given write("CREATE (a:Person {name: 'Alice'})"); write("CREATE (a:Person {name: 'Bob'})"); - try (RxResultConsumeExample example = new RxResultConsumeExample(uri, USER, PASSWORD)) { + try (RxResultConsumeExample example = new RxResultConsumeExample(uri, USER, neo4j.adminPassword())) { // When List names = await(example.getPeopleRxJava()); @@ -635,7 +645,7 @@ void testUseAnotherDatabaseExample() throws Exception { dropDatabase(driver, "examples"); createDatabase(driver, "examples"); - try (DatabaseSelectionExample example = new DatabaseSelectionExample(uri, USER, PASSWORD)) { + try (DatabaseSelectionExample example = new DatabaseSelectionExample(uri, USER, neo4j.adminPassword())) { // When example.useAnotherDatabaseExample(); @@ -647,7 +657,7 @@ void testUseAnotherDatabaseExample() throws Exception { @Test void testReadingValuesExample() throws Exception { - try (ReadingValuesExample example = new ReadingValuesExample(uri, USER, PASSWORD)) { + try (ReadingValuesExample example = new ReadingValuesExample(uri, USER, neo4j.adminPassword())) { assertThat(example.integerFieldIsNull(), is(false)); assertThat(example.integerAsInteger(), is(4)); assertThat(example.integerAsLong(), is(4L)); diff --git a/pom.xml b/pom.xml index ff1e65af96..5b95d462ff 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ ${project.groupId}.${project.artifactId} ${project.basedir} - 1C + 2 parallelizableIT 3.0.0-M6 diff --git a/testkit-tests/pom.xml b/testkit-tests/pom.xml index 15b06fd824..f7a585911a 100644 --- a/testkit-tests/pom.xml +++ b/testkit-tests/pom.xml @@ -19,7 +19,7 @@ ${project.basedir}/.. https://github.com/neo4j-drivers/testkit.git - 4.4 + 5.0 --tests TESTKIT_TESTS INTEGRATION_TESTS STUB_TESTS STRESS_TESTS TLS_TESTS 7200000 From 0f4a6a3da8793a0486d59dc3b15cea7f962f8379 Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov Date: Tue, 22 Nov 2022 14:59:14 +0000 Subject: [PATCH 4/4] Skip tests incompatible with 5.x server --- driver/pom.xml | 2 + .../integration/ConnectionHandlingIT.java | 2 + .../org/neo4j/driver/integration/ErrorIT.java | 34 +- .../driver/integration/SessionBoltV3IT.java | 133 +- .../neo4j/driver/integration/SessionIT.java | 4 + .../driver/integration/SessionResetIT.java | 1322 ++++++++--------- .../neo4j/driver/integration/SummaryIT.java | 97 +- .../integration/TransactionBoltV3IT.java | 58 +- .../integration/async/AsyncSessionIT.java | 5 +- .../integration/async/AsyncTransactionIT.java | 4 + ...RxResultRecordPublisherVerificationIT.java | 80 +- .../neo4j/driver/util/DatabaseExtension.java | 4 - pom.xml | 10 +- testkit-tests/pom.xml | 2 +- 14 files changed, 821 insertions(+), 936 deletions(-) diff --git a/driver/pom.xml b/driver/pom.xml index 3f40a10e9a..17568c4d5b 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -103,10 +103,12 @@ org.reactivestreams reactive-streams-tck + test ch.qos.logback logback-classic + test diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java index ae3f48b72e..bafc392bd0 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -243,6 +244,7 @@ void connectionUsedForTransactionReturnedToThePoolWhenTransactionRolledBack() { @Test void connectionUsedForTransactionReturnedToThePoolWhenTransactionFailsToCommitted() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); try (Session session = driver.session()) { session.run("CREATE CONSTRAINT ON (book:Library) ASSERT exists(book.isbn)"); } diff --git a/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java b/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java index 607c51e3ba..d134650d81 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java @@ -152,22 +152,24 @@ void shouldExplainConnectionError() { @Test void shouldHandleFailureAtRunTime() { - String label = UUID.randomUUID().toString(); // avoid clashes with other tests - - // given - Transaction tx = session.beginTransaction(); - tx.run("CREATE CONSTRAINT ON (a:`" + label + "`) ASSERT a.name IS UNIQUE"); - tx.commit(); - - // and - Transaction anotherTx = session.beginTransaction(); - - // then expect - ClientException e = - assertThrows(ClientException.class, () -> anotherTx.run("CREATE INDEX ON :`" + label + "`(name)")); - anotherTx.rollback(); - assertThat(e.getMessage(), containsString(label)); - assertThat(e.getMessage(), containsString("name")); + if (session.isNeo4j44OrEarlier()) { + String label = UUID.randomUUID().toString(); // avoid clashes with other tests + + // given + Transaction tx = session.beginTransaction(); + tx.run("CREATE CONSTRAINT ON (a:`" + label + "`) ASSERT a.name IS UNIQUE"); + tx.commit(); + + // and + Transaction anotherTx = session.beginTransaction(); + + // then expect + ClientException e = + assertThrows(ClientException.class, () -> anotherTx.run("CREATE INDEX ON :`" + label + "`(name)")); + anotherTx.rollback(); + assertThat(e.getMessage(), containsString(label)); + assertThat(e.getMessage(), containsString("name")); + } } @Test diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java index 6388da523d..1541e7b02d 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java @@ -75,37 +75,42 @@ class SessionBoltV3IT { @Test void shouldSetTransactionMetadata() { - Map metadata = new HashMap<>(); - metadata.put("a", "hello world"); - metadata.put("b", LocalDate.now()); - metadata.put("c", asList(true, false, true)); + if (driver.isNeo4j44OrEarlier()) { + Map metadata = new HashMap<>(); + metadata.put("a", "hello world"); + metadata.put("b", LocalDate.now()); + metadata.put("c", asList(true, false, true)); - TransactionConfig config = - TransactionConfig.builder().withMetadata(metadata).build(); + TransactionConfig config = + TransactionConfig.builder().withMetadata(metadata).build(); - // call listTransactions procedure that should list itself with the specified metadata - Result result = driver.session().run("CALL dbms.listTransactions()", config); - Map receivedMetadata = result.single().get("metaData").asMap(); + // call listTransactions procedure that should list itself with the specified metadata + Result result = driver.session().run("CALL dbms.listTransactions()", config); + Map receivedMetadata = + result.single().get("metaData").asMap(); - assertEquals(metadata, receivedMetadata); + assertEquals(metadata, receivedMetadata); + } } @Test void shouldSetTransactionMetadataAsync() { - Map metadata = new HashMap<>(); - metadata.put("key1", "value1"); - metadata.put("key2", 42L); + if (driver.isNeo4j44OrEarlier()) { + Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + metadata.put("key2", 42L); - TransactionConfig config = - TransactionConfig.builder().withMetadata(metadata).build(); + TransactionConfig config = + TransactionConfig.builder().withMetadata(metadata).build(); - // call listTransactions procedure that should list itself with the specified metadata - CompletionStage> metadataFuture = driver.asyncSession() - .runAsync("CALL dbms.listTransactions()", config) - .thenCompose(ResultCursor::singleAsync) - .thenApply(record -> record.get("metaData").asMap()); + // call listTransactions procedure that should list itself with the specified metadata + CompletionStage> metadataFuture = driver.asyncSession() + .runAsync("CALL dbms.listTransactions()", config) + .thenCompose(ResultCursor::singleAsync) + .thenApply(record -> record.get("metaData").asMap()); - assertEquals(metadata, await(metadataFuture)); + assertEquals(metadata, await(metadataFuture)); + } } @Test @@ -300,50 +305,54 @@ void shouldSendGoodbyeWhenClosingDriver() { } private static void testTransactionMetadataWithAsyncTransactionFunctions(boolean read) { - AsyncSession asyncSession = driver.asyncSession(); - Map metadata = new HashMap<>(); - metadata.put("foo", "bar"); - metadata.put("baz", true); - metadata.put("qux", 12345L); - - TransactionConfig config = - TransactionConfig.builder().withMetadata(metadata).build(); - - // call listTransactions procedure that should list itself with the specified metadata - CompletionStage singleFuture = read - ? asyncSession.readTransactionAsync( - tx -> tx.runAsync("CALL dbms.listTransactions()").thenCompose(ResultCursor::singleAsync), - config) - : asyncSession.writeTransactionAsync( - tx -> tx.runAsync("CALL dbms.listTransactions()").thenCompose(ResultCursor::singleAsync), - config); - - CompletionStage> metadataFuture = - singleFuture.thenApply(record -> record.get("metaData").asMap()); - - assertEquals(metadata, await(metadataFuture)); + if (driver.isNeo4j44OrEarlier()) { + AsyncSession asyncSession = driver.asyncSession(); + Map metadata = new HashMap<>(); + metadata.put("foo", "bar"); + metadata.put("baz", true); + metadata.put("qux", 12345L); + + TransactionConfig config = + TransactionConfig.builder().withMetadata(metadata).build(); + + // call listTransactions procedure that should list itself with the specified metadata + CompletionStage singleFuture = read + ? asyncSession.readTransactionAsync( + tx -> tx.runAsync("CALL dbms.listTransactions()").thenCompose(ResultCursor::singleAsync), + config) + : asyncSession.writeTransactionAsync( + tx -> tx.runAsync("CALL dbms.listTransactions()").thenCompose(ResultCursor::singleAsync), + config); + + CompletionStage> metadataFuture = + singleFuture.thenApply(record -> record.get("metaData").asMap()); + + assertEquals(metadata, await(metadataFuture)); + } } private static void testTransactionMetadataWithTransactionFunctions(boolean read) { - Session session = driver.session(); - Map metadata = new HashMap<>(); - metadata.put("foo", "bar"); - metadata.put("baz", true); - metadata.put("qux", 12345L); - - TransactionConfig config = - TransactionConfig.builder().withMetadata(metadata).build(); - - // call listTransactions procedure that should list itself with the specified metadata - Record single = read - ? session.readTransaction( - tx -> tx.run("CALL dbms.listTransactions()").single(), config) - : session.writeTransaction( - tx -> tx.run("CALL dbms.listTransactions()").single(), config); - - Map receivedMetadata = single.get("metaData").asMap(); - - assertEquals(metadata, receivedMetadata); + if (driver.isNeo4j44OrEarlier()) { + Session session = driver.session(); + Map metadata = new HashMap<>(); + metadata.put("foo", "bar"); + metadata.put("baz", true); + metadata.put("qux", 12345L); + + TransactionConfig config = + TransactionConfig.builder().withMetadata(metadata).build(); + + // call listTransactions procedure that should list itself with the specified metadata + Record single = read + ? session.readTransaction( + tx -> tx.run("CALL dbms.listTransactions()").single(), config) + : session.writeTransaction( + tx -> tx.run("CALL dbms.listTransactions()").single(), config); + + Map receivedMetadata = single.get("metaData").asMap(); + + assertEquals(metadata, receivedMetadata); + } } private static void verifyValidException(Exception error) { diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java index c894f655e5..6fc3507a08 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java @@ -35,6 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -677,6 +678,7 @@ public String execute(Transaction tx) { @Test void shouldThrowRunFailureImmediatelyAndCloseSuccessfully() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); try (Session session = neo4j.driver().session()) { ClientException e = assertThrows(ClientException.class, () -> session.run("RETURN 10 / 0")); @@ -706,6 +708,7 @@ void shouldNotBePossibleToConsumeResultAfterSessionIsClosed() { @Test void shouldThrowRunFailureImmediatelyAfterMultipleSuccessfulRunsAndCloseSuccessfully() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); try (Session session = neo4j.driver().session()) { session.run("CREATE ()"); session.run("CREATE ()"); @@ -717,6 +720,7 @@ void shouldThrowRunFailureImmediatelyAfterMultipleSuccessfulRunsAndCloseSuccessf @Test void shouldThrowRunFailureImmediatelyAndAcceptSubsequentRun() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); try (Session session = neo4j.driver().session()) { session.run("CREATE ()"); session.run("CREATE ()"); diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java index df0727de5a..6ec4be26eb 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java @@ -16,733 +16,595 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -//package org.neo4j.driver.integration; -// -//import static java.util.Collections.newSetFromMap; -//import static java.util.concurrent.CompletableFuture.runAsync; -//import static java.util.concurrent.TimeUnit.MILLISECONDS; -//import static java.util.concurrent.TimeUnit.MINUTES; -//import static java.util.concurrent.TimeUnit.SECONDS; -//import static java.util.stream.IntStream.range; -//import static org.hamcrest.CoreMatchers.containsString; -//import static org.hamcrest.CoreMatchers.equalTo; -//import static org.hamcrest.CoreMatchers.startsWith; -//import static org.hamcrest.Matchers.greaterThan; -//import static org.hamcrest.Matchers.instanceOf; -//import static org.hamcrest.Matchers.lessThanOrEqualTo; -//import static org.hamcrest.junit.MatcherAssert.assertThat; -//import static org.junit.jupiter.api.Assertions.assertEquals; -//import static org.junit.jupiter.api.Assertions.assertNotNull; -//import static org.junit.jupiter.api.Assertions.assertThrows; -//import static org.junit.jupiter.api.Assertions.assertTrue; -//import static org.neo4j.driver.Values.parameters; -//import static org.neo4j.driver.util.DaemonThreadFactory.daemon; -//import static org.neo4j.driver.util.Neo4jRunner.HOME_DIR; -//import static org.neo4j.driver.util.Neo4jSettings.IMPORT_DIR; -//import static org.neo4j.driver.util.Neo4jSettings.TEST_SETTINGS; -//import static org.neo4j.driver.util.TestUtil.activeQueryCount; -//import static org.neo4j.driver.util.TestUtil.activeQueryNames; -//import static org.neo4j.driver.util.TestUtil.await; -//import static org.neo4j.driver.util.TestUtil.awaitAllFutures; -//import static org.neo4j.driver.util.TestUtil.awaitCondition; -// -//import java.io.IOException; -//import java.io.UncheckedIOException; -//import java.net.URI; -//import java.nio.channels.ClosedChannelException; -//import java.nio.file.Files; -//import java.nio.file.Path; -//import java.nio.file.Paths; -//import java.util.ArrayList; -//import java.util.List; -//import java.util.Random; -//import java.util.Set; -//import java.util.concurrent.CompletableFuture; -//import java.util.concurrent.ConcurrentHashMap; -//import java.util.concurrent.CountDownLatch; -//import java.util.concurrent.ExecutionException; -//import java.util.concurrent.ExecutorService; -//import java.util.concurrent.Executors; -//import java.util.concurrent.Future; -//import java.util.concurrent.ThreadLocalRandom; -//import java.util.concurrent.TimeUnit; -//import java.util.concurrent.atomic.AtomicBoolean; -//import java.util.concurrent.atomic.AtomicInteger; -//import java.util.concurrent.atomic.AtomicLong; -//import java.util.concurrent.atomic.AtomicReference; -//import org.hamcrest.CoreMatchers; -//import org.hamcrest.MatcherAssert; -//import org.junit.jupiter.api.AfterEach; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.RegisterExtension; -//import org.neo4j.driver.Driver; -//import org.neo4j.driver.QueryRunner; -//import org.neo4j.driver.Result; -//import org.neo4j.driver.Session; -//import org.neo4j.driver.Transaction; -//import org.neo4j.driver.exceptions.ClientException; -//import org.neo4j.driver.exceptions.Neo4jException; -//import org.neo4j.driver.exceptions.ServiceUnavailableException; -//import org.neo4j.driver.exceptions.TransientException; -//import org.neo4j.driver.util.DatabaseExtension; -//import org.neo4j.driver.util.ParallelizableIT; -// -//@SuppressWarnings("deprecation") -//@ParallelizableIT -//class SessionResetIT { -// private static final int CSV_FILE_SIZE = 10_000; -// private static final int LOAD_CSV_BATCH_SIZE = 10; -// -// private static final String SHORT_QUERY_1 = "CREATE (n:Node {name: 'foo', occupation: 'bar'})"; -// private static final String SHORT_QUERY_2 = "MATCH (n:Node {name: 'foo'}) RETURN count(n)"; -// private static final String LONG_QUERY = "UNWIND range(0, 10000000) AS i CREATE (n:Node {idx: i}) DELETE n"; -// private static final String LONG_PERIODIC_COMMIT_QUERY_TEMPLATE = "USING PERIODIC COMMIT 1 " -// + "LOAD CSV FROM '%s' AS line " -// + "UNWIND range(1, " -// + LOAD_CSV_BATCH_SIZE + ") AS index " + "CREATE (n:Node {id: index, name: line[0], occupation: line[1]})"; -// -// private static final int STRESS_TEST_THREAD_COUNT = Runtime.getRuntime().availableProcessors() * 2; -// private static final long STRESS_TEST_DURATION_MS = SECONDS.toMillis(5); -// private static final String[] STRESS_TEST_QUERIES = {SHORT_QUERY_1, SHORT_QUERY_2, LONG_QUERY}; -// -// @RegisterExtension -// static final DatabaseExtension neo4j = new DatabaseExtension(); -// -// private ExecutorService executor; -// -// @BeforeEach -// void setUp() { -// executor = Executors.newCachedThreadPool(daemon(getClass().getSimpleName() + "-thread")); -// } -// -// @AfterEach -// void tearDown() { -// if (executor != null) { -// executor.shutdownNow(); -// } -// } -// -// @Test -// void shouldTerminateAutoCommitQuery() { -// testQueryTermination(LONG_QUERY, true); -// } -// -// @Test -// void shouldTerminateQueryInUnmanagedTransaction() { -// testQueryTermination(LONG_QUERY, false); -// } -// -// /** -// * It is currently unsafe to terminate periodic commit query because it'll then be half-committed. -// * So the driver give no guarantee when the periodic commit could be terminated. -// * For a user who want to terminate a periodic commit, he or she should use kill query by id. -// */ -// @Test -// void shouldTerminatePeriodicCommitQueryRandomly() { -// Future queryResult = runQueryInDifferentThreadAndResetSession(longPeriodicCommitQuery(), true); -// -// ExecutionException e = assertThrows(ExecutionException.class, () -> queryResult.get(1, MINUTES)); -// assertThat(e.getCause(), instanceOf(Neo4jException.class)); -// -// awaitNoActiveQueries(); -// -// assertThat(countNodes(), lessThanOrEqualTo(((long) CSV_FILE_SIZE) * LOAD_CSV_BATCH_SIZE)); -// } -// -// @Test -// void shouldTerminateAutoCommitQueriesRandomly() throws Exception { -// testRandomQueryTermination(true); -// } -// -// @Test -// void shouldTerminateQueriesInUnmanagedTransactionsRandomly() throws Exception { -// testRandomQueryTermination(false); -// } -// -// @Test -// void shouldRejectNewTransactionWhenOpenTransactionExistsAndShouldFailRunResultOnSessionReset() throws Throwable { -// neo4j.ensureProcedures("longRunningStatement.jar"); -// -// try (Session session = neo4j.driver().session()) { -// Transaction tx1 = session.beginTransaction(); -// -// CompletableFuture txRunFuture = CompletableFuture.runAsync( -// () -> tx1.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); -// -// awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); -// session.reset(); -// -// ClientException e1 = assertThrows(ClientException.class, session::beginTransaction); -// assertThat( -// e1.getMessage(), -// containsString("You cannot begin a transaction on a session with an open transaction")); -// -// ClientException e2 = assertThrows(ClientException.class, () -> tx1.run("RETURN 1")); -// assertThat(e2.getMessage(), containsString("Cannot run more queries in this transaction")); -// -// // Make sure failure from the terminated long running query is propagated -// Neo4jException e3 = assertThrows(Neo4jException.class, () -> await(txRunFuture)); -// assertThat(e3.getMessage(), containsString("The transaction has been terminated")); -// } -// } -// -// @Test -// void shouldSuccessfullyCloseAfterSessionReset() throws Throwable { -// neo4j.ensureProcedures("longRunningStatement.jar"); -// -// try (Session session = neo4j.driver().session()) { -// CompletableFuture.runAsync( -// () -> session.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); -// -// awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); -// session.reset(); -// } -// } -// -// @Test -// void shouldBeAbleToBeginNewTransactionAfterFirstTransactionInterruptedBySessionResetIsClosed() throws Throwable { -// neo4j.ensureProcedures("longRunningStatement.jar"); -// -// try (Session session = neo4j.driver().session()) { -// Transaction tx1 = session.beginTransaction(); -// -// CompletableFuture txRunFuture = runAsync( -// () -> tx1.run("CALL test.driver.longRunningStatement({seconds})", parameters("seconds", 10))); -// -// awaitActiveQueriesToContain("CALL test.driver.longRunningStatement"); -// session.reset(); -// -// Neo4jException e = assertThrows(Neo4jException.class, () -> await(txRunFuture)); -// assertThat(e.getMessage(), containsString("The transaction has been terminated")); -// tx1.close(); -// -// try (Transaction tx2 = session.beginTransaction()) { -// tx2.run("CREATE (n:FirstNode)"); -// tx2.commit(); -// } -// -// Result result = session.run("MATCH (n) RETURN count(n)"); -// long nodes = result.single().get("count(n)").asLong(); -// MatcherAssert.assertThat(nodes, equalTo(1L)); -// } -// } -// -// @Test -// void shouldKillLongRunningQuery() throws Throwable { -// neo4j.ensureProcedures("longRunningStatement.jar"); -// -// final int executionTimeout = 10; // 10s -// final int killTimeout = 1; // 1s -// final AtomicLong startTime = new AtomicLong(-1); -// long endTime; -// -// try (Session session = neo4j.driver().session()) { -// CompletableFuture sessionRunFuture = CompletableFuture.runAsync(() -> { -// // When -// startTime.set(System.currentTimeMillis()); -// session.run( -// "CALL test.driver.longRunningStatement({seconds})", parameters("seconds", executionTimeout)); -// }); -// -// resetSessionAfterTimeout(session, killTimeout); -// -// assertThrows(Neo4jException.class, () -> await(sessionRunFuture)); -// } -// -// endTime = System.currentTimeMillis(); -// assertTrue(startTime.get() > 0); -// assertTrue(endTime - startTime.get() > killTimeout * 1000); // get reset by session.reset -// assertTrue(endTime - startTime.get() < executionTimeout * 1000 / 2); // finished before execution finished -// } -// -// @Test -// void shouldKillLongStreamingResult() throws Throwable { -// neo4j.ensureProcedures("longRunningStatement.jar"); -// // Given -// final int executionTimeout = 10; // 10s -// final int killTimeout = 1; // 1s -// final AtomicInteger recordCount = new AtomicInteger(); -// final AtomicLong startTime = new AtomicLong(-1); -// long endTime; -// -// Neo4jException e = assertThrows(Neo4jException.class, () -> { -// try (Session session = neo4j.driver().session()) { -// Result result = session.run( -// "CALL test.driver.longStreamingResult({seconds})", parameters("seconds", executionTimeout)); -// -// resetSessionAfterTimeout(session, killTimeout); -// -// // When -// startTime.set(System.currentTimeMillis()); -// while (result.hasNext()) { -// result.next(); -// recordCount.incrementAndGet(); -// } -// } -// }); -// -// endTime = System.currentTimeMillis(); -// assertThat(e.getMessage(), containsString("The transaction has been terminated")); -// assertThat(recordCount.get(), greaterThan(1)); -// -// assertTrue(startTime.get() > 0); -// assertTrue(endTime - startTime.get() > killTimeout * 1000); // get reset by session.reset -// assertTrue(endTime - startTime.get() < executionTimeout * 1000 / 2); // finished before execution finished -// } -// -// private void resetSessionAfterTimeout(Session session, int timeout) { -// executor.submit(() -> { -// try { -// Thread.sleep(timeout * 1000); // let the query execute for timeout seconds -// } catch (InterruptedException ignore) { -// } finally { -// session.reset(); // reset the session after timeout -// } -// }); -// } -// -// @Test -// void shouldAllowMoreQueriesAfterSessionReset() { -// // Given -// try (Session session = neo4j.driver().session()) { -// -// session.run("RETURN 1").consume(); -// -// // When reset the state of this session -// session.reset(); -// -// // Then can run successfully more queries without any error -// session.run("RETURN 2").consume(); -// } -// } -// -// @Test -// void shouldAllowMoreTxAfterSessionReset() { -// // Given -// try (Session session = neo4j.driver().session()) { -// try (Transaction tx = session.beginTransaction()) { -// tx.run("RETURN 1"); -// tx.commit(); -// } -// -// // When reset the state of this session -// session.reset(); -// -// // Then can run more Tx -// try (Transaction tx = session.beginTransaction()) { -// tx.run("RETURN 2"); -// tx.commit(); -// } -// } -// } -// -// @Test -// void shouldMarkTxAsFailedAndDisallowRunAfterSessionReset() { -// // Given -// try (Session session = neo4j.driver().session()) { -// Transaction tx = session.beginTransaction(); -// // When reset the state of this session -// session.reset(); -// -// // Then -// Exception e = assertThrows(Exception.class, () -> { -// tx.run("RETURN 1"); -// tx.commit(); -// }); -// assertThat(e.getMessage(), startsWith("Cannot run more queries in this transaction")); -// } -// } -// -// @Test -// void shouldAllowMoreTxAfterSessionResetInTx() { -// // Given -// try (Session session = neo4j.driver().session()) { -// try (Transaction ignore = session.beginTransaction()) { -// // When reset the state of this session -// session.reset(); -// } -// -// // Then can run more Tx -// try (Transaction tx = session.beginTransaction()) { -// tx.run("RETURN 2"); -// tx.commit(); -// } -// } -// } -// -// @Test -// void resetShouldStopQueryWaitingForALock() throws Exception { -// testResetOfQueryWaitingForLock(new NodeIdUpdater() { -// @Override -// void performUpdate( -// Driver driver, -// int nodeId, -// int newNodeId, -// AtomicReference usedSessionRef, -// CountDownLatch latchToWait) -// throws Exception { -// try (Session session = driver.session()) { -// usedSessionRef.set(session); -// latchToWait.await(); -// Result result = updateNodeId(session, nodeId, newNodeId); -// result.consume(); -// } -// } -// }); -// } -// -// @Test -// void resetShouldStopTransactionWaitingForALock() throws Exception { -// testResetOfQueryWaitingForLock(new NodeIdUpdater() { -// @Override -// public void performUpdate( -// Driver driver, -// int nodeId, -// int newNodeId, -// AtomicReference usedSessionRef, -// CountDownLatch latchToWait) -// throws Exception { -// try (Session session = neo4j.driver().session(); -// Transaction tx = session.beginTransaction()) { -// usedSessionRef.set(session); -// latchToWait.await(); -// Result result = updateNodeId(tx, nodeId, newNodeId); -// result.consume(); -// } -// } -// }); -// } -// -// @Test -// void resetShouldStopWriteTransactionWaitingForALock() throws Exception { -// AtomicInteger invocationsOfWork = new AtomicInteger(); -// -// testResetOfQueryWaitingForLock(new NodeIdUpdater() { -// @Override -// public void performUpdate( -// Driver driver, -// int nodeId, -// int newNodeId, -// AtomicReference usedSessionRef, -// CountDownLatch latchToWait) -// throws Exception { -// try (Session session = driver.session()) { -// usedSessionRef.set(session); -// latchToWait.await(); -// -// session.writeTransaction(tx -> { -// invocationsOfWork.incrementAndGet(); -// Result result = updateNodeId(tx, nodeId, newNodeId); -// result.consume(); -// return null; -// }); -// } -// } -// }); -// -// assertEquals(1, invocationsOfWork.get()); -// } -// -// @Test -// void shouldBeAbleToRunMoreQueriesAfterResetOnNoErrorState() { -// try (Session session = neo4j.driver().session()) { -// // Given -// session.reset(); -// -// // When -// Transaction tx = session.beginTransaction(); -// tx.run("CREATE (n:FirstNode)"); -// tx.commit(); -// -// // Then the outcome of both queries should be visible -// Result result = session.run("MATCH (n) RETURN count(n)"); -// long nodes = result.single().get("count(n)").asLong(); -// assertThat(nodes, equalTo(1L)); -// } -// } -// -// @Test -// void shouldHandleResetBeforeRun() { -// try (Session session = neo4j.driver().session(); -// Transaction tx = session.beginTransaction()) { -// session.reset(); -// -// ClientException e = assertThrows(ClientException.class, () -> tx.run("CREATE (n:FirstNode)")); -// assertThat(e.getMessage(), containsString("Cannot run more queries in this transaction")); -// } -// } -// -// @Test -// void shouldHandleResetFromMultipleThreads() throws Throwable { -// Session session = neo4j.driver().session(); -// -// CountDownLatch beforeCommit = new CountDownLatch(1); -// CountDownLatch afterReset = new CountDownLatch(1); -// -// Future txFuture = executor.submit(() -> { -// Transaction tx1 = session.beginTransaction(); -// tx1.run("CREATE (n:FirstNode)"); -// beforeCommit.countDown(); -// afterReset.await(); -// -// // session has been reset, it should not be possible to commit the transaction -// try { -// tx1.commit(); -// } catch (Neo4jException ignore) { -// } -// -// try (Transaction tx2 = session.beginTransaction()) { -// tx2.run("CREATE (n:SecondNode)"); -// tx2.commit(); -// } -// -// return null; -// }); -// -// Future resetFuture = executor.submit(() -> { -// beforeCommit.await(); -// session.reset(); -// afterReset.countDown(); -// return null; -// }); -// -// executor.shutdown(); -// executor.awaitTermination(20, SECONDS); -// -// txFuture.get(20, SECONDS); -// resetFuture.get(20, SECONDS); -// -// assertEquals(0, countNodes("FirstNode")); -// assertEquals(1, countNodes("SecondNode")); -// } -// -// private void testResetOfQueryWaitingForLock(NodeIdUpdater nodeIdUpdater) throws Exception { -// int nodeId = 42; -// int newNodeId1 = 4242; -// int newNodeId2 = 424242; -// -// createNodeWithId(nodeId); -// -// CountDownLatch nodeLocked = new CountDownLatch(1); -// AtomicReference otherSessionRef = new AtomicReference<>(); -// -// try (Session session = neo4j.driver().session(); -// Transaction tx = session.beginTransaction()) { -// Future txResult = nodeIdUpdater.update(nodeId, newNodeId1, otherSessionRef, nodeLocked); -// -// Result result = updateNodeId(tx, nodeId, newNodeId2); -// result.consume(); -// -// nodeLocked.countDown(); -// // give separate thread some time to block on a lock -// Thread.sleep(2_000); -// otherSessionRef.get().reset(); -// -// assertTransactionTerminated(txResult); -// tx.commit(); -// } -// -// try (Session session = neo4j.driver().session()) { -// Result result = session.run("MATCH (n) RETURN n.id AS id"); -// int value = result.single().get("id").asInt(); -// assertEquals(newNodeId2, value); -// } -// } -// -// private void createNodeWithId(int id) { -// try (Session session = neo4j.driver().session()) { -// session.run("CREATE (n {id: $id})", parameters("id", id)); -// } -// } -// -// private static Result updateNodeId(QueryRunner queryRunner, int currentId, int newId) { -// return queryRunner.run( -// "MATCH (n {id: $currentId}) SET n.id = $newId", parameters("currentId", currentId, "newId", newId)); -// } -// -// private static void assertTransactionTerminated(Future work) { -// ExecutionException e = assertThrows(ExecutionException.class, () -> work.get(20, TimeUnit.SECONDS)); -// assertThat(e.getCause(), CoreMatchers.instanceOf(TransientException.class)); -// assertThat(e.getCause().getMessage(), startsWith("The transaction has been terminated")); -// } -// -// private void testRandomQueryTermination(boolean autoCommit) throws Exception { -// Set runningSessions = newSetFromMap(new ConcurrentHashMap<>()); -// AtomicBoolean stop = new AtomicBoolean(); -// List> futures = new ArrayList<>(); -// -// for (int i = 0; i < STRESS_TEST_THREAD_COUNT; i++) { -// futures.add(executor.submit(() -> { -// ThreadLocalRandom random = ThreadLocalRandom.current(); -// while (!stop.get()) { -// runRandomQuery(autoCommit, random, runningSessions, stop); -// } -// })); -// } -// -// long deadline = System.currentTimeMillis() + STRESS_TEST_DURATION_MS; -// while (!stop.get()) { -// if (System.currentTimeMillis() > deadline) { -// stop.set(true); -// } -// -// resetAny(runningSessions); -// -// MILLISECONDS.sleep(30); -// } -// -// awaitAllFutures(futures); -// awaitNoActiveQueries(); -// } -// -// private void runRandomQuery(boolean autoCommit, Random random, Set runningSessions, AtomicBoolean stop) { -// try { -// Session session = neo4j.driver().session(); -// runningSessions.add(session); -// try { -// String query = STRESS_TEST_QUERIES[random.nextInt(STRESS_TEST_QUERIES.length - 1)]; -// runQuery(session, query, autoCommit); -// } finally { -// runningSessions.remove(session); -// session.close(); -// } -// } catch (Throwable error) { -// if (!stop.get() && !isAcceptable(error)) { -// stop.set(true); -// throw error; -// } -// // else it is fine to receive some errors from the driver because -// // sessions are being reset concurrently by the main thread, driver can also be closed concurrently -// } -// } -// -// private void testQueryTermination(String query, boolean autoCommit) { -// Future queryResult = runQueryInDifferentThreadAndResetSession(query, autoCommit); -// ExecutionException e = assertThrows(ExecutionException.class, () -> queryResult.get(10, SECONDS)); -// assertThat(e.getCause(), instanceOf(Neo4jException.class)); -// awaitNoActiveQueries(); -// } -// -// private Future runQueryInDifferentThreadAndResetSession(String query, boolean autoCommit) { -// AtomicReference sessionRef = new AtomicReference<>(); -// -// Future queryResult = runAsync(() -> { -// Session session = neo4j.driver().session(); -// sessionRef.set(session); -// runQuery(session, query, autoCommit); -// }); -// -// awaitActiveQueriesToContain(query); -// -// Session session = sessionRef.get(); -// assertNotNull(session); -// session.reset(); -// -// return queryResult; -// } -// -// private static void runQuery(Session session, String query, boolean autoCommit) { -// if (autoCommit) { -// session.run(query).consume(); -// } else { -// try (Transaction tx = session.beginTransaction()) { -// tx.run(query); -// tx.commit(); -// } -// } -// } -// -// private void awaitNoActiveQueries() { -// awaitCondition(() -> activeQueryCount(neo4j.driver()) == 0); -// } -// -// private void awaitActiveQueriesToContain(String value) { -// awaitCondition(() -> activeQueryNames(neo4j.driver()).stream().anyMatch(query -> query.contains(value))); -// } -// -// private long countNodes() { -// return countNodes(null); -// } -// -// private long countNodes(String label) { -// try (Session session = neo4j.driver().session()) { -// Result result = -// session.run("MATCH (n" + (label == null ? "" : ":" + label) + ") RETURN count(n) AS result"); -// return result.single().get(0).asLong(); -// } -// } -// -// private static void resetAny(Set sessions) { -// sessions.stream().findAny().ifPresent(session -> { -// if (sessions.remove(session)) { -// resetSafely(session); -// } -// }); -// } -// -// private static void resetSafely(Session session) { -// try { -// if (session.isOpen()) { -// session.reset(); -// } -// } catch (ClientException e) { -// if (session.isOpen()) { -// throw e; -// } -// // else this thread lost race with close and it's fine -// } -// } -// -// private static boolean isAcceptable(Throwable error) { -// // get the root cause -// while (error.getCause() != null) { -// error = error.getCause(); -// } -// -// return isTransactionTerminatedException(error) -// || error instanceof ServiceUnavailableException -// || error instanceof ClientException -// || error instanceof ClosedChannelException; -// } -// -// private static boolean isTransactionTerminatedException(Throwable error) { -// return error instanceof TransientException -// && error.getMessage().startsWith("The transaction has been terminated") -// || error.getMessage().startsWith("Trying to execute query in a terminated transaction"); -// } -// -// private static String longPeriodicCommitQuery() { -// URI fileUri = createTmpCsvFile(); -// return String.format(LONG_PERIODIC_COMMIT_QUERY_TEMPLATE, fileUri); -// } -// -// private static URI createTmpCsvFile() { -// try { -// Path importDir = Paths.get(HOME_DIR, TEST_SETTINGS.propertiesMap().get(IMPORT_DIR)); -// Path csvFile = Files.createTempFile(importDir, "test", ".csv"); -// Iterable lines = range(0, CSV_FILE_SIZE).mapToObj(i -> "Foo-" + i + ", Bar-" + i)::iterator; -// return URI.create("file:///" + Files.write(csvFile, lines).getFileName()); -// } catch (IOException e) { -// throw new UncheckedIOException(e); -// } -// } -// -// private abstract class NodeIdUpdater { -// final Future update( -// int nodeId, int newNodeId, AtomicReference usedSessionRef, CountDownLatch latchToWait) { -// return executor.submit(() -> { -// performUpdate(neo4j.driver(), nodeId, newNodeId, usedSessionRef, latchToWait); -// return null; -// }); -// } -// -// abstract void performUpdate( -// Driver driver, -// int nodeId, -// int newNodeId, -// AtomicReference usedSessionRef, -// CountDownLatch latchToWait) -// throws Exception; -// } -//} +package org.neo4j.driver.integration; + +import static java.util.Collections.newSetFromMap; +import static java.util.concurrent.CompletableFuture.runAsync; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.IntStream.range; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.util.DaemonThreadFactory.daemon; +import static org.neo4j.driver.util.TestUtil.activeQueryCount; +import static org.neo4j.driver.util.TestUtil.activeQueryNames; +import static org.neo4j.driver.util.TestUtil.awaitAllFutures; +import static org.neo4j.driver.util.TestUtil.awaitCondition; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.channels.ClosedChannelException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadLocalRandom; +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.stream.Collectors; +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.neo4j.driver.Driver; +import org.neo4j.driver.QueryRunner; +import org.neo4j.driver.Result; +import org.neo4j.driver.Session; +import org.neo4j.driver.Transaction; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.exceptions.Neo4jException; +import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.util.DatabaseExtension; +import org.neo4j.driver.util.ParallelizableIT; + +@SuppressWarnings("deprecation") +@ParallelizableIT +class SessionResetIT { + private static final int CSV_FILE_SIZE = 10_000; + private static final int LOAD_CSV_BATCH_SIZE = 10; + + private static final String SHORT_QUERY_1 = "CREATE (n:Node {name: 'foo', occupation: 'bar'})"; + private static final String SHORT_QUERY_2 = "MATCH (n:Node {name: 'foo'}) RETURN count(n)"; + private static final String LONG_QUERY = "UNWIND range(0, 10000000) AS i CREATE (n:Node {idx: i}) DELETE n"; + private static final String LONG_PERIODIC_COMMIT_QUERY_TEMPLATE = "USING PERIODIC COMMIT 1 " + + "LOAD CSV FROM '%s' AS line " + + "UNWIND range(1, " + + LOAD_CSV_BATCH_SIZE + ") AS index " + "CREATE (n:Node {id: index, name: line[0], occupation: line[1]})"; + + private static final int STRESS_TEST_THREAD_COUNT = Runtime.getRuntime().availableProcessors() * 2; + private static final long STRESS_TEST_DURATION_MS = SECONDS.toMillis(5); + private static final String[] STRESS_TEST_QUERIES = {SHORT_QUERY_1, SHORT_QUERY_2, LONG_QUERY}; + + @RegisterExtension + static final DatabaseExtension neo4j = new DatabaseExtension(); + + private ExecutorService executor; + + @BeforeEach + void setUp() { + executor = Executors.newCachedThreadPool(daemon(getClass().getSimpleName() + "-thread")); + } + + @AfterEach + void tearDown() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @Test + void shouldTerminateAutoCommitQuery() { + testQueryTermination(LONG_QUERY, true); + } + + @Test + void shouldTerminateQueryInUnmanagedTransaction() { + testQueryTermination(LONG_QUERY, false); + } + + /** + * It is currently unsafe to terminate periodic commit query because it'll then be half-committed. + * So the driver give no guarantee when the periodic commit could be terminated. + * For a user who want to terminate a periodic commit, he or she should use kill query by id. + */ + @Test + void shouldTerminatePeriodicCommitQueryRandomly() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); + Future queryResult = runQueryInDifferentThreadAndResetSession(longPeriodicCommitQuery(), true); + + ExecutionException e = assertThrows(ExecutionException.class, () -> queryResult.get(1, MINUTES)); + assertThat(e.getCause(), instanceOf(Neo4jException.class)); + + awaitNoActiveQueries(); + + assertThat(countNodes(), lessThanOrEqualTo(((long) CSV_FILE_SIZE) * LOAD_CSV_BATCH_SIZE)); + } + + @Test + void shouldTerminateAutoCommitQueriesRandomly() throws Exception { + testRandomQueryTermination(true); + } + + @Test + void shouldTerminateQueriesInUnmanagedTransactionsRandomly() throws Exception { + testRandomQueryTermination(false); + } + + private void resetSessionAfterTimeout(Session session, int timeout) { + executor.submit(() -> { + try { + Thread.sleep(timeout * 1000); // let the query execute for timeout seconds + } catch (InterruptedException ignore) { + } finally { + session.reset(); // reset the session after timeout + } + }); + } + + @Test + void shouldAllowMoreQueriesAfterSessionReset() { + // Given + try (Session session = neo4j.driver().session()) { + + session.run("RETURN 1").consume(); + + // When reset the state of this session + session.reset(); + + // Then can run successfully more queries without any error + session.run("RETURN 2").consume(); + } + } + + @Test + void shouldAllowMoreTxAfterSessionReset() { + // Given + try (Session session = neo4j.driver().session()) { + try (Transaction tx = session.beginTransaction()) { + tx.run("RETURN 1"); + tx.commit(); + } + + // When reset the state of this session + session.reset(); + + // Then can run more Tx + try (Transaction tx = session.beginTransaction()) { + tx.run("RETURN 2"); + tx.commit(); + } + } + } + + @Test + void shouldMarkTxAsFailedAndDisallowRunAfterSessionReset() { + // Given + try (Session session = neo4j.driver().session()) { + Transaction tx = session.beginTransaction(); + // When reset the state of this session + session.reset(); + + // Then + Exception e = assertThrows(Exception.class, () -> { + tx.run("RETURN 1"); + tx.commit(); + }); + assertThat(e.getMessage(), startsWith("Cannot run more queries in this transaction")); + } + } + + @Test + void shouldAllowMoreTxAfterSessionResetInTx() { + // Given + try (Session session = neo4j.driver().session()) { + try (Transaction ignore = session.beginTransaction()) { + // When reset the state of this session + session.reset(); + } + + // Then can run more Tx + try (Transaction tx = session.beginTransaction()) { + tx.run("RETURN 2"); + tx.commit(); + } + } + } + + @Test + void resetShouldStopQueryWaitingForALock() throws Exception { + testResetOfQueryWaitingForLock(new NodeIdUpdater() { + @Override + void performUpdate( + Driver driver, + int nodeId, + int newNodeId, + AtomicReference usedSessionRef, + CountDownLatch latchToWait) + throws Exception { + try (Session session = driver.session()) { + usedSessionRef.set(session); + latchToWait.await(); + Result result = updateNodeId(session, nodeId, newNodeId); + result.consume(); + } + } + }); + } + + @Test + void resetShouldStopTransactionWaitingForALock() throws Exception { + testResetOfQueryWaitingForLock(new NodeIdUpdater() { + @Override + public void performUpdate( + Driver driver, + int nodeId, + int newNodeId, + AtomicReference usedSessionRef, + CountDownLatch latchToWait) + throws Exception { + try (Session session = neo4j.driver().session(); + Transaction tx = session.beginTransaction()) { + usedSessionRef.set(session); + latchToWait.await(); + Result result = updateNodeId(tx, nodeId, newNodeId); + result.consume(); + } + } + }); + } + + @Test + void resetShouldStopWriteTransactionWaitingForALock() throws Exception { + AtomicInteger invocationsOfWork = new AtomicInteger(); + + testResetOfQueryWaitingForLock(new NodeIdUpdater() { + @Override + public void performUpdate( + Driver driver, + int nodeId, + int newNodeId, + AtomicReference usedSessionRef, + CountDownLatch latchToWait) + throws Exception { + try (Session session = driver.session()) { + usedSessionRef.set(session); + latchToWait.await(); + + session.writeTransaction(tx -> { + invocationsOfWork.incrementAndGet(); + Result result = updateNodeId(tx, nodeId, newNodeId); + result.consume(); + return null; + }); + } + } + }); + + assertEquals(1, invocationsOfWork.get()); + } + + @Test + void shouldBeAbleToRunMoreQueriesAfterResetOnNoErrorState() { + try (Session session = neo4j.driver().session()) { + // Given + session.reset(); + + // When + Transaction tx = session.beginTransaction(); + tx.run("CREATE (n:FirstNode)"); + tx.commit(); + + // Then the outcome of both queries should be visible + Result result = session.run("MATCH (n) RETURN count(n)"); + long nodes = result.single().get("count(n)").asLong(); + assertThat(nodes, equalTo(1L)); + } + } + + @Test + void shouldHandleResetBeforeRun() { + try (Session session = neo4j.driver().session(); + Transaction tx = session.beginTransaction()) { + session.reset(); + + ClientException e = assertThrows(ClientException.class, () -> tx.run("CREATE (n:FirstNode)")); + assertThat(e.getMessage(), containsString("Cannot run more queries in this transaction")); + } + } + + @Test + void shouldHandleResetFromMultipleThreads() throws Throwable { + Session session = neo4j.driver().session(); + + CountDownLatch beforeCommit = new CountDownLatch(1); + CountDownLatch afterReset = new CountDownLatch(1); + + Future txFuture = executor.submit(() -> { + Transaction tx1 = session.beginTransaction(); + tx1.run("CREATE (n:FirstNode)"); + beforeCommit.countDown(); + afterReset.await(); + + // session has been reset, it should not be possible to commit the transaction + try { + tx1.commit(); + } catch (Neo4jException ignore) { + } + + try (Transaction tx2 = session.beginTransaction()) { + tx2.run("CREATE (n:SecondNode)"); + tx2.commit(); + } + + return null; + }); + + Future resetFuture = executor.submit(() -> { + beforeCommit.await(); + session.reset(); + afterReset.countDown(); + return null; + }); + + executor.shutdown(); + executor.awaitTermination(20, SECONDS); + + txFuture.get(20, SECONDS); + resetFuture.get(20, SECONDS); + + assertEquals(0, countNodes("FirstNode")); + assertEquals(1, countNodes("SecondNode")); + } + + private void testResetOfQueryWaitingForLock(NodeIdUpdater nodeIdUpdater) throws Exception { + assumeTrue(neo4j.isNeo4j44OrEarlier()); + int nodeId = 42; + int newNodeId1 = 4242; + int newNodeId2 = 424242; + + createNodeWithId(nodeId); + + CountDownLatch nodeLocked = new CountDownLatch(1); + AtomicReference otherSessionRef = new AtomicReference<>(); + + try (Session session = neo4j.driver().session(); + Transaction tx = session.beginTransaction()) { + Future txResult = nodeIdUpdater.update(nodeId, newNodeId1, otherSessionRef, nodeLocked); + + Result result = updateNodeId(tx, nodeId, newNodeId2); + result.consume(); + + nodeLocked.countDown(); + // give separate thread some time to block on a lock + Thread.sleep(2_000); + otherSessionRef.get().reset(); + + assertTransactionTerminated(txResult); + tx.commit(); + } + + try (Session session = neo4j.driver().session()) { + Result result = session.run("MATCH (n) RETURN n.id AS id"); + int value = result.single().get("id").asInt(); + assertEquals(newNodeId2, value); + } + } + + private void createNodeWithId(int id) { + try (Session session = neo4j.driver().session()) { + session.run("CREATE (n {id: $id})", parameters("id", id)); + } + } + + private static Result updateNodeId(QueryRunner queryRunner, int currentId, int newId) { + return queryRunner.run( + "MATCH (n {id: $currentId}) SET n.id = $newId", parameters("currentId", currentId, "newId", newId)); + } + + private static void assertTransactionTerminated(Future work) { + ExecutionException e = assertThrows(ExecutionException.class, () -> work.get(20, TimeUnit.SECONDS)); + assertThat(e.getCause(), CoreMatchers.instanceOf(TransientException.class)); + assertThat(e.getCause().getMessage(), startsWith("The transaction has been terminated")); + } + + private void testRandomQueryTermination(boolean autoCommit) throws Exception { + assumeTrue(neo4j.isNeo4j44OrEarlier()); + Set runningSessions = newSetFromMap(new ConcurrentHashMap<>()); + AtomicBoolean stop = new AtomicBoolean(); + List> futures = new ArrayList<>(); + + for (int i = 0; i < STRESS_TEST_THREAD_COUNT; i++) { + futures.add(executor.submit(() -> { + ThreadLocalRandom random = ThreadLocalRandom.current(); + while (!stop.get()) { + runRandomQuery(autoCommit, random, runningSessions, stop); + } + })); + } + + long deadline = System.currentTimeMillis() + STRESS_TEST_DURATION_MS; + while (!stop.get()) { + if (System.currentTimeMillis() > deadline) { + stop.set(true); + } + + resetAny(runningSessions); + + MILLISECONDS.sleep(30); + } + + awaitAllFutures(futures); + awaitNoActiveQueries(); + } + + private void runRandomQuery(boolean autoCommit, Random random, Set runningSessions, AtomicBoolean stop) { + try { + Session session = neo4j.driver().session(); + runningSessions.add(session); + try { + String query = STRESS_TEST_QUERIES[random.nextInt(STRESS_TEST_QUERIES.length - 1)]; + runQuery(session, query, autoCommit); + } finally { + runningSessions.remove(session); + session.close(); + } + } catch (Throwable error) { + if (!stop.get() && !isAcceptable(error)) { + stop.set(true); + throw error; + } + // else it is fine to receive some errors from the driver because + // sessions are being reset concurrently by the main thread, driver can also be closed concurrently + } + } + + private void testQueryTermination(String query, boolean autoCommit) { + assumeTrue(neo4j.isNeo4j44OrEarlier()); + Future queryResult = runQueryInDifferentThreadAndResetSession(query, autoCommit); + ExecutionException e = assertThrows(ExecutionException.class, () -> queryResult.get(10, SECONDS)); + assertThat(e.getCause(), instanceOf(Neo4jException.class)); + awaitNoActiveQueries(); + } + + private Future runQueryInDifferentThreadAndResetSession(String query, boolean autoCommit) { + AtomicReference sessionRef = new AtomicReference<>(); + + Future queryResult = runAsync(() -> { + Session session = neo4j.driver().session(); + sessionRef.set(session); + runQuery(session, query, autoCommit); + }); + + awaitActiveQueriesToContain(query); + + Session session = sessionRef.get(); + assertNotNull(session); + session.reset(); + + return queryResult; + } + + private static void runQuery(Session session, String query, boolean autoCommit) { + if (autoCommit) { + session.run(query).consume(); + } else { + try (Transaction tx = session.beginTransaction()) { + tx.run(query); + tx.commit(); + } + } + } + + private void awaitNoActiveQueries() { + awaitCondition(() -> activeQueryCount(neo4j.driver()) == 0); + } + + private void awaitActiveQueriesToContain(String value) { + awaitCondition(() -> activeQueryNames(neo4j.driver()).stream().anyMatch(query -> query.contains(value))); + } + + private long countNodes() { + return countNodes(null); + } + + private long countNodes(String label) { + try (Session session = neo4j.driver().session()) { + Result result = + session.run("MATCH (n" + (label == null ? "" : ":" + label) + ") RETURN count(n) AS result"); + return result.single().get(0).asLong(); + } + } + + private static void resetAny(Set sessions) { + sessions.stream().findAny().ifPresent(session -> { + if (sessions.remove(session)) { + resetSafely(session); + } + }); + } + + private static void resetSafely(Session session) { + try { + if (session.isOpen()) { + session.reset(); + } + } catch (ClientException e) { + if (session.isOpen()) { + throw e; + } + // else this thread lost race with close and it's fine + } + } + + private static boolean isAcceptable(Throwable error) { + // get the root cause + while (error.getCause() != null) { + error = error.getCause(); + } + + return isTransactionTerminatedException(error) + || error instanceof ServiceUnavailableException + || error instanceof ClientException + || error instanceof ClosedChannelException; + } + + private static boolean isTransactionTerminatedException(Throwable error) { + return error instanceof TransientException + && error.getMessage().startsWith("The transaction has been terminated") + || error.getMessage().startsWith("Trying to execute query in a terminated transaction"); + } + + private static String longPeriodicCommitQuery() { + String fileUri = createTmpCsvFile(); + return String.format(LONG_PERIODIC_COMMIT_QUERY_TEMPLATE, fileUri); + } + + private static String createTmpCsvFile() { + String lines = range(0, CSV_FILE_SIZE) + .mapToObj(i -> "Foo-" + i + ", Bar-" + i) + .collect(Collectors.joining(System.lineSeparator())); + try { + return neo4j.addImportFile("iris", ".csv", lines); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private abstract class NodeIdUpdater { + final Future update( + int nodeId, int newNodeId, AtomicReference usedSessionRef, CountDownLatch latchToWait) { + return executor.submit(() -> { + performUpdate(neo4j.driver(), nodeId, newNodeId, usedSessionRef, latchToWait); + return null; + }); + } + + abstract void performUpdate( + Driver driver, + int nodeId, + int newNodeId, + AtomicReference usedSessionRef, + CountDownLatch latchToWait) + throws Exception; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java b/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java index 6aa0c63216..27bcae7474 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java @@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.neo4j.driver.SessionConfig.forDatabase; import java.util.List; @@ -106,55 +107,64 @@ void shouldContainTimeInformation() { @Test void shouldContainCorrectStatistics() { - assertThat(session.run("CREATE (n)").consume().counters().nodesCreated(), equalTo(1)); - assertThat(session.run("MATCH (n) DELETE (n)").consume().counters().nodesDeleted(), equalTo(1)); - - assertThat(session.run("CREATE ()-[:KNOWS]->()").consume().counters().relationshipsCreated(), equalTo(1)); - assertThat( - session.run("MATCH ()-[r:KNOWS]->() DELETE r") - .consume() - .counters() - .relationshipsDeleted(), - equalTo(1)); - - assertThat(session.run("CREATE (n:ALabel)").consume().counters().labelsAdded(), equalTo(1)); - assertThat(session.run("CREATE (n {magic: 42})").consume().counters().propertiesSet(), equalTo(1)); - assertTrue(session.run("CREATE (n {magic: 42})").consume().counters().containsUpdates()); - assertThat( - session.run("MATCH (n:ALabel) REMOVE n:ALabel ") - .consume() - .counters() - .labelsRemoved(), - equalTo(1)); - - assertThat( - session.run("CREATE INDEX ON :ALabel(prop)") - .consume() - .counters() - .indexesAdded(), - equalTo(1)); - assertThat( - session.run("DROP INDEX ON :ALabel(prop)").consume().counters().indexesRemoved(), equalTo(1)); - - assertThat( - session.run("CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE") - .consume() - .counters() - .constraintsAdded(), - equalTo(1)); - assertThat( - session.run("DROP CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE") - .consume() - .counters() - .constraintsRemoved(), - equalTo(1)); + if (neo4j.isNeo4j44OrEarlier()) { + assertThat(session.run("CREATE (n)").consume().counters().nodesCreated(), equalTo(1)); + assertThat(session.run("MATCH (n) DELETE (n)").consume().counters().nodesDeleted(), equalTo(1)); + + assertThat( + session.run("CREATE ()-[:KNOWS]->()").consume().counters().relationshipsCreated(), equalTo(1)); + assertThat( + session.run("MATCH ()-[r:KNOWS]->() DELETE r") + .consume() + .counters() + .relationshipsDeleted(), + equalTo(1)); + + assertThat(session.run("CREATE (n:ALabel)").consume().counters().labelsAdded(), equalTo(1)); + assertThat( + session.run("CREATE (n {magic: 42})").consume().counters().propertiesSet(), equalTo(1)); + assertTrue( + session.run("CREATE (n {magic: 42})").consume().counters().containsUpdates()); + assertThat( + session.run("MATCH (n:ALabel) REMOVE n:ALabel ") + .consume() + .counters() + .labelsRemoved(), + equalTo(1)); + + assertThat( + session.run("CREATE INDEX ON :ALabel(prop)") + .consume() + .counters() + .indexesAdded(), + equalTo(1)); + assertThat( + session.run("DROP INDEX ON :ALabel(prop)") + .consume() + .counters() + .indexesRemoved(), + equalTo(1)); + + assertThat( + session.run("CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE") + .consume() + .counters() + .constraintsAdded(), + equalTo(1)); + assertThat( + session.run("DROP CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE") + .consume() + .counters() + .constraintsRemoved(), + equalTo(1)); + } } @Test @EnabledOnNeo4jWith(Neo4jFeature.BOLT_V4) void shouldGetSystemUpdates() throws Throwable { try (Session session = neo4j.driver().session(forDatabase("system"))) { - Result result = session.run("CREATE USER foo SET PASSWORD 'bar'"); + Result result = session.run("CREATE USER foo SET PASSWORD 'Testing5'"); assertThat(result.consume().counters().containsUpdates(), equalTo(false)); assertThat(result.consume().counters().containsSystemUpdates(), equalTo(true)); } @@ -162,6 +172,7 @@ void shouldGetSystemUpdates() throws Throwable { @Test void shouldContainCorrectQueryType() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); assertThat(session.run("MATCH (n) RETURN 1").consume().queryType(), equalTo(QueryType.READ_ONLY)); assertThat(session.run("CREATE (n)").consume().queryType(), equalTo(QueryType.WRITE_ONLY)); assertThat(session.run("CREATE (n) RETURN (n)").consume().queryType(), equalTo(QueryType.READ_WRITE)); diff --git a/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java b/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java index 7625de9d95..4c91dd782b 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java @@ -57,41 +57,45 @@ class TransactionBoltV3IT { @Test void shouldSetTransactionMetadata() { - Map metadata = new HashMap<>(); - metadata.put("key1", "value1"); - metadata.put("key2", 42L); - metadata.put("key3", false); + if (driver.isNeo4j44OrEarlier()) { + Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + metadata.put("key2", 42L); + metadata.put("key3", false); - TransactionConfig config = - TransactionConfig.builder().withMetadata(metadata).build(); + TransactionConfig config = + TransactionConfig.builder().withMetadata(metadata).build(); - try (Transaction tx = driver.session().beginTransaction(config)) { - tx.run("RETURN 1").consume(); + try (Transaction tx = driver.session().beginTransaction(config)) { + tx.run("RETURN 1").consume(); - verifyTransactionMetadata(metadata); + verifyTransactionMetadata(metadata); + } } } @Test void shouldSetTransactionMetadataAsync() { - Map metadata = new HashMap<>(); - metadata.put("hello", "world"); - metadata.put("key", ZonedDateTime.now()); - - TransactionConfig config = - TransactionConfig.builder().withMetadata(metadata).build(); - - CompletionStage txFuture = driver.asyncSession() - .beginTransactionAsync(config) - .thenCompose(tx -> tx.runAsync("RETURN 1") - .thenCompose(ResultCursor::consumeAsync) - .thenApply(ignore -> tx)); - - AsyncTransaction transaction = await(txFuture); - try { - verifyTransactionMetadata(metadata); - } finally { - await(transaction.rollbackAsync()); + if (driver.isNeo4j44OrEarlier()) { + Map metadata = new HashMap<>(); + metadata.put("hello", "world"); + metadata.put("key", ZonedDateTime.now()); + + TransactionConfig config = + TransactionConfig.builder().withMetadata(metadata).build(); + + CompletionStage txFuture = driver.asyncSession() + .beginTransactionAsync(config) + .thenCompose(tx -> tx.runAsync("RETURN 1") + .thenCompose(ResultCursor::consumeAsync) + .thenApply(ignore -> tx)); + + AsyncTransaction transaction = await(txFuture); + try { + verifyTransactionMetadata(metadata); + } finally { + await(transaction.rollbackAsync()); + } } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionIT.java index 888402e618..76f4b573f8 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionIT.java @@ -32,6 +32,7 @@ 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 static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.neo4j.driver.SessionConfig.builder; import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.util.Futures.failedFuture; @@ -275,7 +276,7 @@ void shouldExposeResultSummaryForExplainQuery() { // server versions; that is why do fuzzy assertions in this test based on string content String planAsString = summary.plan().toString().toLowerCase(); assertThat(planAsString, containsString("create")); - assertThat(planAsString, containsString("expand")); + assertThat(planAsString, containsString("apply")); assertNull(summary.profile()); assertEquals(0, summary.notifications().size()); assertThat(summary, containsResultAvailableAfterAndResultConsumedAfter()); @@ -646,6 +647,7 @@ public CompletionStage execute(AsyncTransaction tx) { @Test void shouldNotPropagateRunFailureWhenClosed() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); session.runAsync("RETURN 10 / 0"); await(session.closeAsync()); @@ -653,6 +655,7 @@ void shouldNotPropagateRunFailureWhenClosed() { @Test void shouldPropagateRunFailureImmediately() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); ClientException e = assertThrows(ClientException.class, () -> await(session.runAsync("RETURN 10 / 0"))); assertThat(e.getMessage(), containsString("/ by zero")); diff --git a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java index b8831bfa89..f1ef9d0850 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java @@ -31,6 +31,7 @@ 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 static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.neo4j.driver.SessionConfig.builder; import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.InternalBookmark.parse; @@ -610,6 +611,7 @@ void shouldUpdateSessionBookmarkAfterCommit() { @Test void shouldFailToCommitWhenQueriesFail() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); AsyncTransaction tx = await(session.beginTransactionAsync()); tx.runAsync("CREATE (:TestNode)"); @@ -637,6 +639,7 @@ void shouldFailToCommitWhenRunFailed() { @Test void shouldFailToCommitWhenBlockedRunFailed() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); AsyncTransaction tx = await(session.beginTransactionAsync()); ClientException runException = assertThrows(ClientException.class, () -> await(tx.runAsync("RETURN 42 / 0"))); @@ -658,6 +661,7 @@ void shouldRollbackSuccessfullyWhenRunFailed() { @Test void shouldRollbackSuccessfullyWhenBlockedRunFailed() { + assumeTrue(neo4j.isNeo4j44OrEarlier()); AsyncTransaction tx = await(session.beginTransactionAsync()); assertThrows(ClientException.class, () -> await(tx.runAsync("RETURN 42 / 0"))); diff --git a/driver/src/test/java/org/neo4j/driver/tck/reactive/RxResultRecordPublisherVerificationIT.java b/driver/src/test/java/org/neo4j/driver/tck/reactive/RxResultRecordPublisherVerificationIT.java index f3c2f90281..519bc90543 100644 --- a/driver/src/test/java/org/neo4j/driver/tck/reactive/RxResultRecordPublisherVerificationIT.java +++ b/driver/src/test/java/org/neo4j/driver/tck/reactive/RxResultRecordPublisherVerificationIT.java @@ -18,6 +18,14 @@ */ package org.neo4j.driver.tck.reactive; +import static org.neo4j.driver.Values.parameters; + +import java.time.Duration; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Record; +import org.neo4j.driver.reactive.RxResult; +import org.neo4j.driver.reactive.RxSession; import org.reactivestreams.Publisher; import org.reactivestreams.tck.PublisherVerification; import org.reactivestreams.tck.TestEnvironment; @@ -27,85 +35,63 @@ import org.testng.SkipException; import org.testng.annotations.BeforeClass; -import java.time.Duration; +@Testcontainers(disabledWithoutDocker = true) +public class RxResultRecordPublisherVerificationIT extends PublisherVerification { + private static final Neo4jContainer NEO4J_CONTAINER = new Neo4jContainer<>("neo4j:4.4").withAdminPassword(null); -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Record; -import org.neo4j.driver.reactive.RxResult; -import org.neo4j.driver.reactive.RxSession; + private static final long MAX_NUMBER_OF_RECORDS = 30000; -import static org.neo4j.driver.Values.parameters; + private static final Duration TIMEOUT = Duration.ofSeconds(10); + private static final Duration TIMEOUT_FOR_NO_SIGNALS = Duration.ofSeconds(1); + private static final Duration PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS = Duration.ofSeconds(1); -@Testcontainers( disabledWithoutDocker = true ) -public class RxResultRecordPublisherVerificationIT extends PublisherVerification -{ - private static final Neo4jContainer NEO4J_CONTAINER = new Neo4jContainer<>( "neo4j:4.4" ) - .withAdminPassword( null ); - - private final static long MAX_NUMBER_OF_RECORDS = 30000; - - private static final Duration TIMEOUT = Duration.ofSeconds( 10 ); - private static final Duration TIMEOUT_FOR_NO_SIGNALS = Duration.ofSeconds( 1 ); - private static final Duration PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS = Duration.ofSeconds( 1 ); - - private final static String QUERY = "UNWIND RANGE(1, $numberOfRecords) AS n RETURN 'String Number' + n"; + private static final String QUERY = "UNWIND RANGE(1, $numberOfRecords) AS n RETURN 'String Number' + n"; private Driver driver; - public RxResultRecordPublisherVerificationIT() - { - super( new TestEnvironment( TIMEOUT.toMillis(), TIMEOUT_FOR_NO_SIGNALS.toMillis() ), - PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS.toMillis() ); + public RxResultRecordPublisherVerificationIT() { + super( + new TestEnvironment(TIMEOUT.toMillis(), TIMEOUT_FOR_NO_SIGNALS.toMillis()), + PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS.toMillis()); } @BeforeClass - public void beforeClass() - { - if ( !isDockerAvailable() ) - { - throw new SkipException( "Docker is unavailable" ); + public void beforeClass() { + if (!isDockerAvailable()) { + throw new SkipException("Docker is unavailable"); } NEO4J_CONTAINER.start(); - driver = GraphDatabase.driver( NEO4J_CONTAINER.getBoltUrl() ); + driver = GraphDatabase.driver(NEO4J_CONTAINER.getBoltUrl()); } - public void afterClass() - { + public void afterClass() { NEO4J_CONTAINER.stop(); } @Override - public long maxElementsFromPublisher() - { + public long maxElementsFromPublisher() { return MAX_NUMBER_OF_RECORDS; } @Override - public Publisher createPublisher( long elements ) - { + public Publisher createPublisher(long elements) { RxSession session = driver.rxSession(); - RxResult result = session.run( QUERY, parameters( "numberOfRecords", elements ) ); + RxResult result = session.run(QUERY, parameters("numberOfRecords", elements)); return result.records(); } @Override - public Publisher createFailedPublisher() - { + public Publisher createFailedPublisher() { RxSession session = driver.rxSession(); - RxResult result = session.run( "INVALID" ); + RxResult result = session.run("INVALID"); return result.records(); } - boolean isDockerAvailable() - { - try - { + boolean isDockerAvailable() { + try { DockerClientFactory.instance().client(); return true; - } - catch ( Throwable ex ) - { + } catch (Throwable ex) { return false; } } diff --git a/driver/src/test/java/org/neo4j/driver/util/DatabaseExtension.java b/driver/src/test/java/org/neo4j/driver/util/DatabaseExtension.java index c0eea975f1..db780ace50 100644 --- a/driver/src/test/java/org/neo4j/driver/util/DatabaseExtension.java +++ b/driver/src/test/java/org/neo4j/driver/util/DatabaseExtension.java @@ -246,10 +246,6 @@ public boolean isNeo4j44OrEarlier() { return isNeo4jVersionOrEarlier(4, 4); } - public boolean isNeo4j43OrEarlier() { - return isNeo4jVersionOrEarlier(4, 3); - } - private boolean isNeo4jVersionOrEarlier(int major, int minor) { try (Session session = driver.session()) { String neo4jVersion = session.readTransaction( diff --git a/pom.xml b/pom.xml index 5b95d462ff..3129c34244 100644 --- a/pom.xml +++ b/pom.xml @@ -48,12 +48,12 @@ 1.2.0 1.70 1.2.11 - 2.13.2 - 1.18.22 - 21.3.1 - 1.8.3 + 2.13.3 + 1.18.24 + 21.3.2.1 + 1.9.1 1.17.1 - 4.4.8 + 4.4.14 diff --git a/testkit-tests/pom.xml b/testkit-tests/pom.xml index f7a585911a..15b06fd824 100644 --- a/testkit-tests/pom.xml +++ b/testkit-tests/pom.xml @@ -19,7 +19,7 @@ ${project.basedir}/.. https://github.com/neo4j-drivers/testkit.git - 5.0 + 4.4 --tests TESTKIT_TESTS INTEGRATION_TESTS STUB_TESTS STRESS_TESTS TLS_TESTS 7200000