diff --git a/.github/workflows/gradle-release.yml b/.github/workflows/gradle-release.yml deleted file mode 100644 index d35c0aa73f8..00000000000 --- a/.github/workflows/gradle-release.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Gradle Release - -on: - release: - types: [published] - -jobs: - build: - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Verify CI check status - run: .github/workflows/scripts/check_ci_status.sh $GITHUB_SHA - - name: Release with Gradle - run: ./gradlew -Pversion=${GITHUB_REF##*/} release --scan --no-daemon -i - env: - BINTRAY_USER: ${{ secrets.BINTRAY_USER }} - BINTRAY_API_KEY: ${{ secrets.BINTRAY_API_KEY }} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..ec7a5d343d9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +if: tag is present + +language: java +jdk: + - openjdk8 + +sudo: required +services: + - docker + +jobs: + include: + + - stage: deploy + sudo: false + services: [] + install: skip + script: skip + deploy: + provider: script + script: ./gradlew -Pversion=$TRAVIS_TAG release --scan --no-daemon -i + on: + tags: true + branch: master diff --git a/core/build.gradle b/core/build.gradle index 2d76c38c0de..25aaa396a60 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -108,14 +108,14 @@ dependencies { testCompile 'org.apache.httpcomponents:httpclient:4.5.9' testCompile 'redis.clients:jedis:3.2.0' testCompile 'com.rabbitmq:amqp-client:5.8.0' - testCompile 'org.mongodb:mongo-java-driver:3.12.0' + testCompile 'org.mongodb:mongo-java-driver:3.12.1' testCompile ('org.mockito:mockito-core:3.2.4') { exclude(module: 'hamcrest-core') } // Synthetic JAR used for MountableFileTest and DirectoryTarResourceTest testCompile files('testlib/repo/fakejar/fakejar/0/fakejar-0.jar') - testCompile 'org.assertj:assertj-core:3.14.0' + testCompile 'org.assertj:assertj-core:3.15.0' testCompile project(':test-support') jarFileTestCompileOnly "org.projectlombok:lombok:${lombok.version}" diff --git a/core/src/main/java/org/testcontainers/utility/ResourceReaper.java b/core/src/main/java/org/testcontainers/utility/ResourceReaper.java index f46db952c37..f74905e60ef 100644 --- a/core/src/main/java/org/testcontainers/utility/ResourceReaper.java +++ b/core/src/main/java/org/testcontainers/utility/ResourceReaper.java @@ -15,10 +15,11 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.rnorth.ducttape.ratelimits.RateLimiter; +import org.rnorth.ducttape.ratelimits.RateLimiterBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.DockerClientFactory; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -49,6 +50,11 @@ public final class ResourceReaper { private static final Logger LOGGER = LoggerFactory.getLogger(ResourceReaper.class); private static final List>> DEATH_NOTE = new ArrayList<>(); + private static final RateLimiter RYUK_ACK_RATE_LIMITER = RateLimiterBuilder + .newBuilder() + .withRate(4, TimeUnit.SECONDS) + .withConstantThroughput() + .build(); private static ResourceReaper instance; private final DockerClient dockerClient; @@ -110,34 +116,36 @@ public static String start(String hostIpAddress, DockerClient client) { DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, () -> { while (true) { - int index = 0; - try(Socket clientSocket = new Socket(hostIpAddress, ryukPort)) { - FilterRegistry registry = new FilterRegistry(clientSocket.getInputStream(), clientSocket.getOutputStream()); - - synchronized (DEATH_NOTE) { - while (true) { - if (DEATH_NOTE.size() <= index) { - try { - DEATH_NOTE.wait(1_000); - continue; - } catch (InterruptedException e) { - throw new RuntimeException(e); + RYUK_ACK_RATE_LIMITER.doWhenReady(() -> { + int index = 0; + try(Socket clientSocket = new Socket(hostIpAddress, ryukPort)) { + FilterRegistry registry = new FilterRegistry(clientSocket.getInputStream(), clientSocket.getOutputStream()); + + synchronized (DEATH_NOTE) { + while (true) { + if (DEATH_NOTE.size() <= index) { + try { + DEATH_NOTE.wait(1_000); + continue; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + List> filters = DEATH_NOTE.get(index); + boolean isAcknowledged = registry.register(filters); + if (isAcknowledged) { + log.debug("Received 'ACK' from Ryuk"); + ryukScheduledLatch.countDown(); + index++; + } else { + log.debug("Didn't receive 'ACK' from Ryuk. Will retry to send filters."); } - } - List> filters = DEATH_NOTE.get(index); - boolean isAcknowledged = registry.register(filters); - if (isAcknowledged) { - log.debug("Received 'ACK' from Ryuk"); - ryukScheduledLatch.countDown(); - index++; - } else { - log.debug("Didn't receive 'ACK' from Ryuk. Will retry to send filters."); } } + } catch (IOException e) { + log.warn("Can not connect to Ryuk at {}:{}", hostIpAddress, ryukPort, e); } - } catch (IOException e) { - log.warn("Can not connect to Ryuk at {}:{}", hostIpAddress, ryukPort, e); - } + }); } }, "testcontainers-ryuk" @@ -341,12 +349,12 @@ public void unregisterNetwork(String identifier) { public void unregisterContainer(String identifier) { registeredContainers.remove(identifier); } - + public void registerImageForCleanup(String dockerImageName) { setHook(); registeredImages.add(dockerImageName); } - + private void removeImage(String dockerImageName) { LOGGER.trace("Removing image tagged {}", dockerImageName); try { diff --git a/core/src/test/resources/compose-build-test/Dockerfile b/core/src/test/resources/compose-build-test/Dockerfile index 521e2d23b80..f5df49b0152 100644 --- a/core/src/test/resources/compose-build-test/Dockerfile +++ b/core/src/test/resources/compose-build-test/Dockerfile @@ -1,3 +1,3 @@ -FROM redis +FROM redis:2.6.17 CMD ["redis-server"] diff --git a/core/src/test/resources/compose-build-test/docker-compose.yml b/core/src/test/resources/compose-build-test/docker-compose.yml index 333b2fa46f1..5b9f3e2c21b 100644 --- a/core/src/test/resources/compose-build-test/docker-compose.yml +++ b/core/src/test/resources/compose-build-test/docker-compose.yml @@ -3,4 +3,4 @@ services: customredis: build: . normalredis: - image: redis:latest + image: redis:2.6.17 diff --git a/docs/modules/databases/index.md b/docs/modules/databases/index.md index e6d62ac6fda..d7654cc6a33 100644 --- a/docs/modules/databases/index.md +++ b/docs/modules/databases/index.md @@ -45,7 +45,7 @@ As long as you have Testcontainers and the appropriate JDBC driver on your class _N.B:_ * _TC needs to be on your application's classpath at runtime for this to work_ -* _For Spring Boot you need to specify the driver manually `spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver`_ +* _For Spring Boot (Before version `2.3.0`) you need to specify the driver manually `spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver`_ **Original URL**: `jdbc:mysql:5.7.22://localhost:3306/databasename` diff --git a/docs/modules/databases/orientdb.md b/docs/modules/databases/orientdb.md new file mode 100644 index 00000000000..80ed96f9548 --- /dev/null +++ b/docs/modules/databases/orientdb.md @@ -0,0 +1,78 @@ +# OrientDB Module + +!!! note + This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy. + + +This module helps running [OrientDB](https://orientdb.org/download) using Testcontainers. + +Note that it's based on the [official Docker image](https://hub.docker.com/_/orientdb/) provided by OrientDB. + +## Usage example + +Declare your Testcontainer as a `@ClassRule` or `@Rule` in a JUnit 4 test or as static or member attribute of a JUnit 5 test annotated with `@Container` as you would with other Testcontainers. +You can call `getDbUrl()` OrientDB container and build the `ODatabaseSession` by your own, but a more useful `getSession()` method is provided. +On the JVM you would most likely use the [Java driver](https://github.com/). + +The following example uses the JUnit 5 extension `@Testcontainers` and demonstrates both the usage of the Java Client: + +```java tab="JUnit 5 example" +@Testcontainers +public class ExampleTest { + + @Container + private static OrientDBContainer container = new OrientDBContainer(); + + @Test + void testDbCreation() { + + final ODatabaseSession session = container.getSession(); + + session.command("CREATE CLASS Person EXTENDS V"); + session.command("INSERT INTO Person set name='john'"); + session.command("INSERT INTO Person set name='jane'"); + + assertThat(session.query("SELECT FROM Person").stream()).hasSize(2); + } + +} +``` + +You are not limited to Unit tests and can of course use an instance of the OrientDB Testcontainer in vanilla Java code as well. + + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +```groovy tab='Gradle' +testCompile "org.testcontainers:orientdb:{{latest_version}}" +``` + +```xml tab='Maven' + + org.testcontainers + orientdb + {{latest_version}} + test + +``` + +!!! hint + Add the OrientDB Java client if you plan to access the Testcontainer: + + ```groovy tab='Gradle' + compile "com.orientechnologies:orientdb-client:3.0.24" + ``` + + ```xml tab='Maven' + + com.orientechnologies + orientdb-client + 3.0.24 + + ``` + + + + diff --git a/docs/quickstart/junit_5_quickstart.md b/docs/quickstart/junit_5_quickstart.md index 5a07af24731..179a87a5448 100644 --- a/docs/quickstart/junit_5_quickstart.md +++ b/docs/quickstart/junit_5_quickstart.md @@ -27,6 +27,7 @@ testCompile "org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion" testCompile "org.junit.jupiter:junit-jupiter-params:$junitJupiterVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion" testCompile "org.testcontainers:testcontainers:{{latest_version}}" +testCompile "org.testcontainers:junit-jupiter:{{latest_version}}" ``` ```xml tab='Maven' diff --git a/examples/linked-container/build.gradle b/examples/linked-container/build.gradle index 3e603dec908..f1977ea2c0b 100644 --- a/examples/linked-container/build.gradle +++ b/examples/linked-container/build.gradle @@ -7,7 +7,7 @@ repositories { } dependencies { compileOnly 'org.slf4j:slf4j-api:1.7.30' - implementation 'com.squareup.okhttp3:okhttp:3.14.5' + implementation 'com.squareup.okhttp3:okhttp:3.14.6' implementation 'org.json:json:20180813' testImplementation 'org.postgresql:postgresql:42.2.9' testImplementation 'ch.qos.logback:logback-classic:1.2.3' diff --git a/examples/selenium-container/build.gradle b/examples/selenium-container/build.gradle index 5b6d71934b9..db570bf67ac 100644 --- a/examples/selenium-container/build.gradle +++ b/examples/selenium-container/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '2.2.2.RELEASE' + id 'org.springframework.boot' version '2.2.4.RELEASE' } apply plugin: 'io.spring.dependency-management' diff --git a/examples/spring-boot/build.gradle b/examples/spring-boot/build.gradle index bea11bf0909..e74e04a95e5 100644 --- a/examples/spring-boot/build.gradle +++ b/examples/spring-boot/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '2.2.2.RELEASE' + id 'org.springframework.boot' version '2.2.4.RELEASE' } apply plugin: 'io.spring.dependency-management' diff --git a/mkdocs.yml b/mkdocs.yml index e0a2a223f0a..f66f382ccb0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,4 +85,4 @@ nav: - contributing.md - contributing_docs.md extra: - latest_version: 1.12.3 + latest_version: 1.12.5 diff --git a/modules/couchbase/build.gradle b/modules/couchbase/build.gradle index 27074f1bff7..d6a7e51681e 100644 --- a/modules/couchbase/build.gradle +++ b/modules/couchbase/build.gradle @@ -2,7 +2,7 @@ description = "Testcontainers :: Couchbase" dependencies { compile project(':testcontainers') - compile 'com.couchbase.client:java-client:2.7.11' + compile 'com.couchbase.client:java-client:2.7.12' testCompile project(':test-support') } diff --git a/modules/database-commons/build.gradle b/modules/database-commons/build.gradle index ea3e0b07360..d8738a6cb69 100644 --- a/modules/database-commons/build.gradle +++ b/modules/database-commons/build.gradle @@ -3,5 +3,5 @@ description = "Testcontainers :: Database-Commons" dependencies { compile project(':testcontainers') - testCompile 'org.assertj:assertj-core:3.14.0' + testCompile 'org.assertj:assertj-core:3.15.0' } diff --git a/modules/elasticsearch/build.gradle b/modules/elasticsearch/build.gradle index 2201efff48a..41d5b0f27ab 100644 --- a/modules/elasticsearch/build.gradle +++ b/modules/elasticsearch/build.gradle @@ -2,6 +2,6 @@ description = "TestContainers :: elasticsearch" dependencies { compile project(':testcontainers') - testCompile "org.elasticsearch.client:elasticsearch-rest-client:7.5.1" + testCompile "org.elasticsearch.client:elasticsearch-rest-client:7.5.2" testCompile "org.elasticsearch.client:transport:6.7.1" } diff --git a/modules/junit-jupiter/build.gradle b/modules/junit-jupiter/build.gradle index a2698da90d9..7bb6393a586 100644 --- a/modules/junit-jupiter/build.gradle +++ b/modules/junit-jupiter/build.gradle @@ -2,20 +2,20 @@ description = "Testcontainers :: JUnit Jupiter Extension" dependencies { compile project(':testcontainers') - compile 'org.junit.jupiter:junit-jupiter-api:5.5.2' + compile 'org.junit.jupiter:junit-jupiter-api:5.6.0' testCompile project(':mysql') testCompile project(':postgresql') testCompile 'com.zaxxer:HikariCP:3.4.2' testCompile 'redis.clients:jedis:3.2.0' - testCompile 'org.apache.httpcomponents:httpclient:4.5.10' + testCompile 'org.apache.httpcomponents:httpclient:4.5.11' testCompile ('org.mockito:mockito-core:3.2.4') { exclude(module: 'hamcrest-core') } testRuntime 'org.postgresql:postgresql:42.2.9' testRuntime 'mysql:mysql-connector-java:8.0.19' - testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.5.2' + testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.6.0' } test { diff --git a/modules/kafka/build.gradle b/modules/kafka/build.gradle index d064e46c1dc..3960cd96b69 100644 --- a/modules/kafka/build.gradle +++ b/modules/kafka/build.gradle @@ -4,6 +4,6 @@ dependencies { compile project(':testcontainers') testCompile 'org.apache.kafka:kafka-clients:2.4.0' - testCompile 'org.assertj:assertj-core:3.14.0' + testCompile 'org.assertj:assertj-core:3.15.0' testCompile 'com.google.guava:guava:23.0' } diff --git a/modules/localstack/build.gradle b/modules/localstack/build.gradle index 819419c57ee..6d87d282d90 100644 --- a/modules/localstack/build.gradle +++ b/modules/localstack/build.gradle @@ -6,4 +6,5 @@ dependencies { compileOnly 'com.amazonaws:aws-java-sdk-s3:1.11.683' testCompile 'com.amazonaws:aws-java-sdk-s3:1.11.683' testCompile 'com.amazonaws:aws-java-sdk-sqs:1.11.636' + testCompile 'com.amazonaws:aws-java-sdk-logs:1.11.636' } diff --git a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java index 36eaacba89b..e21cc735447 100644 --- a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java +++ b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java @@ -154,7 +154,7 @@ public enum Service { SSM("ssm", 4583), SECRETSMANAGER("secretsmanager", 4584), STEPFUNCTIONS("stepfunctions", 4585), - CLOUDWATCHLOGS("cloudwatchlogs", 4586), + CLOUDWATCHLOGS("logs", 4586), STS("sts", 4592), IAM("iam", 4593); diff --git a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java index 1b222c0fbc7..7ad31f6340f 100644 --- a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java +++ b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java @@ -1,6 +1,12 @@ package org.testcontainers.containers.localstack; +import com.amazonaws.services.logs.AWSLogs; +import com.amazonaws.services.logs.AWSLogsClientBuilder; +import com.amazonaws.services.logs.model.CreateLogGroupRequest; +import com.amazonaws.services.logs.model.CreateLogGroupResult; +import com.amazonaws.services.logs.model.DescribeLogGroupsRequest; +import com.amazonaws.services.logs.model.LogGroup; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.Bucket; @@ -13,6 +19,7 @@ import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; @@ -31,6 +38,7 @@ import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue; import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3; import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS; +import static org.testcontainers.containers.localstack.LocalStackContainer.Service.CLOUDWATCHLOGS; /** * Tests for Localstack Container, used both in bridge network (exposed to host) and docker network modes. @@ -48,7 +56,7 @@ public static class WithoutNetwork { // without_network { @ClassRule public static LocalStackContainer localstack = new LocalStackContainer() - .withServices(S3, SQS); + .withServices(S3, SQS, CLOUDWATCHLOGS); // } @Test @@ -95,6 +103,20 @@ public void sqsTestOverBridgeNetwork() { .count(); assertEquals("the sent message can be received", 1L, messageCount); } + + @Test + @Ignore("Fails due to https://github.com/localstack/localstack/issues/1434") + public void cloudWatchLogsTestOverBridgeNetwork() { + AWSLogs logs = AWSLogsClientBuilder.standard() + .withEndpointConfiguration(localstack.getEndpointConfiguration(CLOUDWATCHLOGS)) + .withCredentials(localstack.getDefaultCredentialsProvider()).build(); + + logs.createLogGroup(new CreateLogGroupRequest("foo")); + + List groups = logs.describeLogGroups().getLogGroups(); + assertEquals("One log group should be created", 1, groups.size()); + assertEquals("Name of created log group is [foo]", "foo", groups.get(0).getLogGroupName()); + } } public static class WithNetwork { @@ -105,7 +127,7 @@ public static class WithNetwork { public static LocalStackContainer localstackInDockerNetwork = new LocalStackContainer() .withNetwork(network) .withNetworkAliases("notthis", "localstack") // the last alias is used for HOSTNAME_EXTERNAL - .withServices(S3, SQS); + .withServices(S3, SQS, CLOUDWATCHLOGS); // } @ClassRule @@ -139,6 +161,11 @@ public void sqsTestOverDockerNetwork() throws Exception { assertTrue("the sent message can be received", message.contains("\"Body\": \"test\"")); } + @Test + public void cloudWatchLogsTestOverDockerNetwork() throws Exception { + runAwsCliAgainstDockerNetworkContainer("logs create-log-group --log-group-name foo", CLOUDWATCHLOGS.getPort()); + } + private String runAwsCliAgainstDockerNetworkContainer(String command, final int port) throws Exception { final String[] commandParts = String.format("/usr/bin/aws --region eu-west-1 %s --endpoint-url http://localstack:%d --no-verify-ssl", command, port).split(" "); final Container.ExecResult execResult = awsCliInDockerNetwork.execInContainer(commandParts); diff --git a/modules/mariadb/build.gradle b/modules/mariadb/build.gradle index fac747aa68e..c7d1dc96676 100644 --- a/modules/mariadb/build.gradle +++ b/modules/mariadb/build.gradle @@ -3,7 +3,7 @@ description = "Testcontainers :: JDBC :: MariaDB" dependencies { compile project(':jdbc') - testCompile 'org.mariadb.jdbc:mariadb-java-client:2.5.3' + testCompile 'org.mariadb.jdbc:mariadb-java-client:2.5.4' testCompile 'com.zaxxer:HikariCP-java6:2.3.13' testCompile 'commons-dbutils:commons-dbutils:1.7' testCompile 'org.apache.tomcat:tomcat-jdbc:9.0.30' diff --git a/modules/neo4j/build.gradle b/modules/neo4j/build.gradle index 00e1e36f8b7..9361824fdd6 100644 --- a/modules/neo4j/build.gradle +++ b/modules/neo4j/build.gradle @@ -30,5 +30,5 @@ dependencies { compile project(":testcontainers") testCompile "org.neo4j.driver:neo4j-java-driver:1.7.5" - testCompile 'org.assertj:assertj-core:3.14.0' + testCompile 'org.assertj:assertj-core:3.15.0' } diff --git a/modules/orientdb/build.gradle b/modules/orientdb/build.gradle new file mode 100644 index 00000000000..ffa1350172a --- /dev/null +++ b/modules/orientdb/build.gradle @@ -0,0 +1,11 @@ +description = "TestContainers :: Orientdb" + +dependencies { + compile project(":testcontainers") + + compile "com.orientechnologies:orientdb-client:3.0.24" + + testCompile 'org.assertj:assertj-core:3.12.0' + testCompile 'org.apache.tinkerpop:gremlin-driver:3.3.4' + testCompile "com.orientechnologies:orientdb-gremlin:3.0.18" +} diff --git a/modules/orientdb/src/main/java/org/testcontainers/containers/OrientDBContainer.java b/modules/orientdb/src/main/java/org/testcontainers/containers/OrientDBContainer.java new file mode 100644 index 00000000000..b368d96bf8c --- /dev/null +++ b/modules/orientdb/src/main/java/org/testcontainers/containers/OrientDBContainer.java @@ -0,0 +1,153 @@ +package org.testcontainers.containers; + +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.orientechnologies.orient.core.db.ODatabaseSession; +import com.orientechnologies.orient.core.db.ODatabaseType; +import com.orientechnologies.orient.core.db.OrientDB; +import com.orientechnologies.orient.core.db.OrientDBConfig; +import lombok.NonNull; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.containers.wait.strategy.WaitAllStrategy; +import org.testcontainers.containers.wait.strategy.WaitStrategy; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Optional; + +import static java.net.HttpURLConnection.HTTP_OK; + +/** + * @author robfrank + */ +public class OrientDBContainer extends GenericContainer { + private static final Logger LOGGER = LoggerFactory.getLogger(OrientDBContainer.class); + + private static final String DEFAULT_IMAGE_NAME = "orientdb"; + private static final String DEFAULT_TAG = "3.0.24-tp3"; + private static final String DOCKER_IMAGE_NAME = DEFAULT_IMAGE_NAME + ":" + DEFAULT_TAG; + + private static final String DEFAULT_USERNAME = "admin"; + private static final String DEFAULT_PASSWORD = "admin"; + private static final String DEFAULT_SERVER_PASSWORD = "root"; + + private static final String DEFAULT_DATABASE_NAME = "testcontainers"; + + private static final int DEFAULT_BINARY_PORT = 2424; + private static final int DEFAULT_HTTP_PORT = 2480; + + private String databaseName; + private String serverPassword; + private Optional scriptPath = Optional.empty(); + + private OrientDB orientDB; + private ODatabaseSession session; + + public OrientDBContainer() { + this(DOCKER_IMAGE_NAME); + } + + public OrientDBContainer(@NonNull String dockerImageName) { + super(dockerImageName); + + serverPassword = DEFAULT_SERVER_PASSWORD; + databaseName = DEFAULT_DATABASE_NAME; + + WaitStrategy waitForHttp = new HttpWaitStrategy() + .forPort(DEFAULT_HTTP_PORT) + .forStatusCodeMatching(response -> response == HTTP_OK); + + waitStrategy = new WaitAllStrategy() + .withStrategy(Wait.forListeningPort()) + .withStrategy(waitForHttp) + .withStartupTimeout(Duration.ofMinutes(2)); + } + + @Override + protected void configure() { + addExposedPorts(DEFAULT_BINARY_PORT, DEFAULT_HTTP_PORT); + addEnv("ORIENTDB_ROOT_PASSWORD", serverPassword); + } + + public String getDatabaseName() { + return databaseName; + } + + public String getTestQueryString() { + return "SELECT FROM V"; + } + + public OrientDBContainer withDatabaseName(final String databaseName) { + this.databaseName = databaseName; + return self(); + } + + public OrientDBContainer withServerPassword(final String serverPassword) { + this.serverPassword = serverPassword; + return self(); + } + + public OrientDBContainer withScriptPath(String scriptPath) { + this.scriptPath = Optional.of(scriptPath); + return self(); + } + + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + orientDB = new OrientDB(getServerUrl(), "root", serverPassword, OrientDBConfig.defaultConfig()); + } + + public OrientDB getOrientDB() { + return orientDB; + } + + public String getServerUrl() { + return "remote:" + getContainerIpAddress() + ":" + getMappedPort(2424); + } + + public String getDbUrl() { + return getServerUrl() + "/" + databaseName; + } + + public ODatabaseSession getSession() { + return getSession(DEFAULT_USERNAME, DEFAULT_PASSWORD); + } + + public synchronized ODatabaseSession getSession(String username, String password) { + orientDB.createIfNotExists(databaseName, ODatabaseType.PLOCAL); + + if (session == null) { + session = orientDB.open(databaseName, username, password); + + scriptPath.ifPresent(path -> loadScript(path, session)); + } + return session; + } + + private void loadScript(String path, ODatabaseSession session) { + try { + URL resource = getClass().getClassLoader().getResource(path); + + if (resource == null) { + LOGGER.warn("Could not load classpath init script: {}", scriptPath); + throw new RuntimeException("Could not load classpath init script: " + scriptPath + ". Resource not found."); + } + + String script = IOUtils.toString(resource, StandardCharsets.UTF_8); + + session.execute("sql", script); + } catch (IOException e) { + LOGGER.warn("Could not load classpath init script: {}", scriptPath); + throw new RuntimeException("Could not load classpath init script: " + scriptPath, e); + } catch (UnsupportedOperationException e) { + LOGGER.error("Error while executing init script: {}", scriptPath, e); + throw new RuntimeException("Error while executing init script: " + scriptPath, e); + } + } + +} diff --git a/modules/orientdb/src/test/java/org/testcontainers/containers/OrientDBContainerTest.java b/modules/orientdb/src/test/java/org/testcontainers/containers/OrientDBContainerTest.java new file mode 100644 index 00000000000..0a067b375b6 --- /dev/null +++ b/modules/orientdb/src/test/java/org/testcontainers/containers/OrientDBContainerTest.java @@ -0,0 +1,73 @@ +package org.testcontainers.containers; + +import com.orientechnologies.orient.core.db.ODatabaseSession; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.*; + +/** + * @author robfrank + */ +public class OrientDBContainerTest { + + @Test + public void shouldReturnTheSameSession() { + try (OrientDBContainer container = new OrientDBContainer()) { + container.start(); + + final ODatabaseSession session = container.getSession(); + final ODatabaseSession session2 = container.getSession(); + + assertThat(session).isSameAs(session2); + } + } + + @Test + public void shouldInitializeWithCommands() { + try (OrientDBContainer container = new OrientDBContainer()) { + container.start(); + + final ODatabaseSession session = container.getSession(); + + session.command("CREATE CLASS Person EXTENDS V"); + session.command("INSERT INTO Person set name='john'"); + session.command("INSERT INTO Person set name='jane'"); + + assertThat(session.query("SELECT FROM Person").stream()).hasSize(2); + } + } + + @Test + public void shouldQueryWithGremlin() { + + try (OrientDBContainer container = new OrientDBContainer()) { + container.start(); + + final ODatabaseSession session = container.getSession("admin", "admin"); + + session.command("CREATE CLASS Person EXTENDS V"); + session.command("INSERT INTO Person set name='john'"); + session.command("INSERT INTO Person set name='jane'"); + + assertThat(session.execute("gremlin", + "g.V().hasLabel('Person')").stream()).hasSize(2); + } + } + + @Test + public void shouldInitializeDatabaseFromScript() { + try (OrientDBContainer container = new OrientDBContainer() + .withScriptPath("initscript.osql") + .withDatabaseName("persons")) { + + container.start(); + + assertThat(container.getDbUrl()) + .isEqualTo("remote:" + container.getContainerIpAddress() + ":" + container.getMappedPort(2424) + "/persons"); + + final ODatabaseSession session = container.getSession(); + + assertThat(session.query("SELECT FROM Person").stream()).hasSize(4); + } + } +} diff --git a/modules/orientdb/src/test/resources/initscript.osql b/modules/orientdb/src/test/resources/initscript.osql new file mode 100644 index 00000000000..e1ba22bbb3e --- /dev/null +++ b/modules/orientdb/src/test/resources/initscript.osql @@ -0,0 +1,6 @@ +CREATE CLASS Person EXTENDS V; + +INSERT INTO Person set name="john"; +INSERT INTO Person set name="paul"; +INSERT INTO Person set name="luke"; +INSERT INTO Person set name="albert"; diff --git a/modules/orientdb/src/test/resources/logback-test.xml b/modules/orientdb/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..7bd6a94d827 --- /dev/null +++ b/modules/orientdb/src/test/resources/logback-test.xml @@ -0,0 +1,18 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/modules/pulsar/build.gradle b/modules/pulsar/build.gradle index 81be68579d7..88d783fc221 100644 --- a/modules/pulsar/build.gradle +++ b/modules/pulsar/build.gradle @@ -4,5 +4,5 @@ dependencies { compile project(':testcontainers') testCompile group: 'org.apache.pulsar', name: 'pulsar-client', version: '2.5.0' - testCompile group: 'org.assertj', name: 'assertj-core', version: '3.14.0' + testCompile group: 'org.assertj', name: 'assertj-core', version: '3.15.0' } diff --git a/modules/spock/build.gradle b/modules/spock/build.gradle index d964e67c5cf..f26858947ce 100644 --- a/modules/spock/build.gradle +++ b/modules/spock/build.gradle @@ -11,7 +11,7 @@ dependencies { testCompile project(':mysql') testCompile project(':postgresql') testCompile 'com.zaxxer:HikariCP:3.4.2' - testCompile 'org.apache.httpcomponents:httpclient:4.5.10' + testCompile 'org.apache.httpcomponents:httpclient:4.5.11' testRuntime 'org.postgresql:postgresql:42.2.9' testRuntime 'mysql:mysql-connector-java:8.0.19' diff --git a/modules/vault/build.gradle b/modules/vault/build.gradle index 0a68c9310e0..1c8a4083b9c 100644 --- a/modules/vault/build.gradle +++ b/modules/vault/build.gradle @@ -4,7 +4,7 @@ dependencies { compile project(':testcontainers') testCompile 'com.bettercloud:vault-java-driver:5.1.0' - testCompile 'io.rest-assured:rest-assured:4.1.2' - testCompile 'org.assertj:assertj-core:3.14.0' + testCompile 'io.rest-assured:rest-assured:4.2.0' + testCompile 'org.assertj:assertj-core:3.15.0' } diff --git a/requirements.txt b/requirements.txt index a936d8a0a95..dd80510a4b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -mkdocs +mkdocs==1.0.4 -e git+https://github.com/rnorth/mkdocs-codeinclude-plugin#egg=mkdocs_codeinclude_plugin -mkdocs-material -mkdocs-markdownextradata-plugin +mkdocs-material==4.6.0 +mkdocs-markdownextradata-plugin==0.1.1 +markdown>=3.1,<3.2