diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 938c733d..32899a97 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -35,3 +35,8 @@ updates: package-ecosystem: "bundler" schedule: interval: "monthly" + + - directory: "/testing/testcontainers/java" + package-ecosystem: "gradle" + schedule: + interval: "monthly" diff --git a/.github/workflows/testcontainers-java.yml b/.github/workflows/testcontainers-java.yml new file mode 100644 index 00000000..014898aa --- /dev/null +++ b/.github/workflows/testcontainers-java.yml @@ -0,0 +1,47 @@ +name: Testcontainers for Java +on: + + pull_request: + branches: [ main ] + paths: + - '.github/workflows/testcontainers-java.yml' + - 'testing/testcontainers/java/**' + push: + branches: [ main ] + paths: + - '.github/workflows/testcontainers-java.yml' + - 'testing/testcontainers/java/**' + + # Allow job to be triggered manually. + workflow_dispatch: + +# Cancel in-progress jobs when pushing to the same branch. +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +jobs: + test: + name: "CrateDB: ${{ matrix.cratedb-version }} + on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ "ubuntu-latest" ] + + steps: + + - name: Acquire sources + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: "17" + cache: "gradle" + + - name: Run software tests + run: | + cd testing/testcontainers/java + ./gradlew check diff --git a/README.rst b/README.rst index b7127337..bf73cc9a 100644 --- a/README.rst +++ b/README.rst @@ -26,5 +26,8 @@ Layout CrateDB is part of a larger software stack. Those resources may also be used within "reference architecture" types of documentation. +- The ``testing`` folder contains reference implementations about how to use + different kinds of test layers for testing your applications with CrateDB. + .. _CrateDB: https://github.com/crate/crate diff --git a/testing/testcontainers/java/.gitignore b/testing/testcontainers/java/.gitignore new file mode 100644 index 00000000..27427779 --- /dev/null +++ b/testing/testcontainers/java/.gitignore @@ -0,0 +1,8 @@ +.gradle/ +.idea +*.iws +*.iml +*.ipr +build/ +out/ +target/ diff --git a/testing/testcontainers/java/.java-version b/testing/testcontainers/java/.java-version new file mode 100644 index 00000000..98d9bcb7 --- /dev/null +++ b/testing/testcontainers/java/.java-version @@ -0,0 +1 @@ +17 diff --git a/testing/testcontainers/java/README.rst b/testing/testcontainers/java/README.rst new file mode 100644 index 00000000..6d172fa6 --- /dev/null +++ b/testing/testcontainers/java/README.rst @@ -0,0 +1,62 @@ +####################### +Testcontainers for Java +####################### + +*How to run integration tests of Java applications with CrateDB.* + + +***** +About +***** + +Introduction +============ + +`Testcontainers for Java`_ is a Java library that supports JUnit tests, +providing lightweight, throwaway instances of common databases suitable +for integration testing scenarios. + +The `Testcontainers CrateDB Module`_ will provide your application test +framework with a single-node CrateDB instance. You will be able to choose +the `CrateDB OCI image`_ variant by version, or by selecting the ``nightly`` +release. + +What's inside +============= + +This directory includes different canonical examples how to use those +components within test harnesses of custom applications. Currently, +all test cases are based on JUnit 4. + + +***** +Usage +***** + +1. Make sure Java 17 is installed. +2. Run CrateDB:: + + docker run -it --rm --publish=4200:4200 --publish=5432:5432 \ + crate:latest -Cdiscovery.type=single-node + +3. Invoke example application:: + + ./gradlew run --args="jdbc:crate://localhost:5432/" + ./gradlew run --args="jdbc:postgresql://localhost:5432/" + +3. Invoke software tests:: + + # Run all tests. + ./gradlew test + + # Run individual tests. + ./gradlew test --tests TestFunctionScope + + # Run test case showing how to select CrateDB version per environment variable. + export CRATEDB_VERSION=nightly + ./gradlew test --tests TestSharedSingletonMatrix + + +.. _CrateDB OCI image: https://hub.docker.com/_/crate +.. _Testcontainers for Java: https://github.com/testcontainers/testcontainers-java +.. _Testcontainers CrateDB Module: https://www.testcontainers.org/modules/databases/cratedb/ diff --git a/testing/testcontainers/java/build.gradle b/testing/testcontainers/java/build.gradle new file mode 100644 index 00000000..13c818b4 --- /dev/null +++ b/testing/testcontainers/java/build.gradle @@ -0,0 +1,70 @@ +/** + * An example application for demonstrating "Testcontainers for Java" with CrateDB and the PostgreSQL JDBC driver. + */ + +buildscript { + repositories { + mavenCentral() + } +} + +plugins { + id 'application' + id 'com.adarshr.test-logger' version '3.2.0' + id 'idea' + id 'java' +} + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + implementation 'org.postgresql:postgresql:42.6.0' + implementation 'io.crate:crate-jdbc:2.6.0' + implementation 'org.slf4j:slf4j-api:2.0.7' + implementation 'org.slf4j:slf4j-simple:2.0.7' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.testcontainers:testcontainers:1.18.0' + testImplementation 'org.testcontainers:cratedb:1.18.0' + testImplementation 'org.testcontainers:postgresql:1.18.0' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +jar { + archiveBaseName = 'cratedb-example-testcontainers-java' + archiveVersion = '0.0.1-SNAPSHOT' +} + +sourceSets { + main { + java.srcDirs += [ + "src/generated/java", + "src/main/java", + ] + } +} + +test { + dependsOn 'cleanTest' +} + +apply plugin: "java" +apply plugin: "application" + +application { + mainClass = 'io.crate.example.testing.Application' +} + +ext.javaMainClass = "io.crate.example.testing.Application" + + +idea.module.inheritOutputDirs = true +processResources.destinationDir = compileJava.destinationDir +compileJava.dependsOn processResources diff --git a/testing/testcontainers/java/gradle/wrapper/gradle-wrapper.jar b/testing/testcontainers/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/testing/testcontainers/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/testing/testcontainers/java/gradle/wrapper/gradle-wrapper.properties b/testing/testcontainers/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..070cb702 --- /dev/null +++ b/testing/testcontainers/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/testing/testcontainers/java/gradlew b/testing/testcontainers/java/gradlew new file mode 100755 index 00000000..a69d9cb6 --- /dev/null +++ b/testing/testcontainers/java/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/testing/testcontainers/java/gradlew.bat b/testing/testcontainers/java/gradlew.bat new file mode 100644 index 00000000..9109989e --- /dev/null +++ b/testing/testcontainers/java/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/testing/testcontainers/java/src/main/java/io/crate/example/testing/Application.java b/testing/testcontainers/java/src/main/java/io/crate/example/testing/Application.java new file mode 100644 index 00000000..f6cb7ade --- /dev/null +++ b/testing/testcontainers/java/src/main/java/io/crate/example/testing/Application.java @@ -0,0 +1,84 @@ +/** + * An example application for demonstrating "Testcontainers for Java" with CrateDB and the PostgreSQL JDBC driver. + * + * - https://github.com/crate/crate + * - https://github.com/testcontainers/testcontainers-java + * - https://www.testcontainers.org/modules/databases/cratedb/ + * - https://github.com/pgjdbc/pgjdbc + */ + +package io.crate.example.testing; + +import java.io.IOException; +import java.sql.*; +import java.util.Locale; +import java.util.Properties; + +public class Application { + + public String dsn; + public String user; + + public Application(String connectionUrl) { + dsn = connectionUrl; + user = "crate"; + } + + public Application(String connectionUrl, String userName) { + dsn = connectionUrl; + user = userName; + } + + public static void main(String[] args) throws IOException, SQLException { + if (args.length != 1) { + throw new IOException("ERROR: Need a single argument, the database connection URL. Example: jdbc:postgresql://localhost:5432/"); + } + String connectionUrl = args[0]; + Application app = new Application(connectionUrl); + app.querySummitsTable(); + System.out.println("Ready."); + } + + /** + * Example database conversation: Query the built-in `sys.summits` table of CrateDB. + */ + public void querySummitsTable() throws IOException, SQLException { + this.query("SELECT * FROM sys.summits LIMIT 3;"); + } + + public void query(String sql) throws IOException, SQLException { + + Properties connectionProps = new Properties(); + connectionProps.put("user", user); + + Connection sqlConnection = DriverManager.getConnection(dsn, connectionProps); + sqlConnection.setAutoCommit(true); + if (sqlConnection.isClosed()) { + throw new IOException("ERROR: Unable to open connection to database"); + } + try (Statement stmt = sqlConnection.createStatement()) { + boolean checkResults = stmt.execute(sql); + if (checkResults) { + ResultSet rs = stmt.getResultSet(); + while (rs.next()) { + System.out.printf(Locale.ENGLISH, "> row %d\n", rs.getRow()); + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + System.out.printf( + Locale.ENGLISH, + ">> col %d: %s: %s\n", + i, + metaData.getColumnName(i), + rs.getObject(i)); + } + } + } else { + throw new IOException("ERROR: Result is empty"); + } + } + sqlConnection.close(); + + } + +} diff --git a/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestClassScope.java b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestClassScope.java new file mode 100644 index 00000000..b1fbfbf7 --- /dev/null +++ b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestClassScope.java @@ -0,0 +1,40 @@ +package io.crate.example.testing; + +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; +import java.sql.SQLException; + +import org.testcontainers.cratedb.CrateDBContainer; +import org.testcontainers.utility.DockerImageName; + + +/** + * Class-scoped testcontainer instance with JUnit 4 @Rule/@ClassRule integration. + * + * In case you can't use the URL support, or need to fine-tune the container, you can instantiate it yourself. + * Note that if you use @Rule, you will be given an isolated container for each test method. + * If you use @ClassRule, you will get on isolated container for all the methods in the test class. + * + * - https://www.testcontainers.org/modules/databases/jdbc/#database-container-objects + * - https://www.testcontainers.org/test_framework_integration/junit_4/#ruleclassrule-integration + */ +public class TestClassScope { + @ClassRule + public static CrateDBContainer cratedb = new CrateDBContainer(DockerImageName.parse("crate:5.2")); + + @Test + public void testReadSummits() throws SQLException, IOException { + + // Get JDBC URL to CrateDB instance. + String connectionUrl = cratedb.getJdbcUrl(); + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example test. + Application app = new Application(connectionUrl); + app.querySummitsTable(); + + } + +} diff --git a/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestFunctionScope.java b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestFunctionScope.java new file mode 100644 index 00000000..579779a9 --- /dev/null +++ b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestFunctionScope.java @@ -0,0 +1,40 @@ +package io.crate.example.testing; + +import org.junit.Rule; +import org.junit.Test; +import org.testcontainers.cratedb.CrateDBContainer; +import org.testcontainers.utility.DockerImageName; + +import java.io.IOException; +import java.sql.SQLException; + + +/** + * Function-scoped testcontainer instance with JUnit 4 @Rule/@ClassRule integration. + * + * In case you can't use the URL support, or need to fine-tune the container, you can instantiate it yourself. + * Note that if you use @Rule, you will be given an isolated container for each test method. + * If you use @ClassRule, you will get on isolated container for all the methods in the test class. + * + * - https://www.testcontainers.org/modules/databases/jdbc/#database-container-objects + * - https://www.testcontainers.org/test_framework_integration/junit_4/#ruleclassrule-integration + */ +public class TestFunctionScope { + + @Rule + public CrateDBContainer cratedb = new CrateDBContainer(DockerImageName.parse("crate:5.2")); + + @Test + public void testReadSummits() throws SQLException, IOException { + + // Get JDBC URL to CrateDB instance. + String connectionUrl = cratedb.getJdbcUrl(); + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example test. + Application app = new Application(connectionUrl); + app.querySummitsTable(); + + } + +} diff --git a/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestJDBCURLScheme.java b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestJDBCURLScheme.java new file mode 100644 index 00000000..debe86c4 --- /dev/null +++ b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestJDBCURLScheme.java @@ -0,0 +1,82 @@ +package io.crate.example.testing; + +import org.junit.Test; + +import java.io.IOException; +import java.sql.SQLException; + + +/** + * Database containers launched via Testcontainers "TC" JDBC URL scheme. + * + * - https://www.testcontainers.org/modules/databases/jdbc/#database-containers-launched-via-jdbc-url-scheme + * - https://www.testcontainers.org/features/reuse/ + */ +public class TestJDBCURLScheme { + + /** + * Launch container with PostgreSQL 15. + */ + @Test + public void testReadSummitsPostgreSQL() throws SQLException, IOException { + + String connectionUrl = "jdbc:tc:postgresql:15:///"; + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example query. + Application app = new Application(connectionUrl, "postgres"); + app.query("SELECT * FROM information_schema.sql_features LIMIT 3;"); + } + + /** + * Launch container with CrateDB 5.2. + */ + @Test + public void testReadSummitsCrateDB() throws SQLException, IOException { + + // NOTE: Please note `jdbc:tc:crate` will not work, only `jdbc:tc:cratedb`. + // => `java.lang.UnsupportedOperationException: Database name crate not supported` + String connectionUrl = "jdbc:tc:cratedb:5.2://localhost/doc?user=crate"; + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example query. + Application app = new Application(connectionUrl); + app.querySummitsTable(); + + } + + /** + * Launch container with CrateDB 5.2, using daemon mode. + * + * https://www.testcontainers.org/modules/databases/jdbc/#running-container-in-daemon-mode + */ + @Test + public void testReadSummitsCrateDBWithDaemon() throws SQLException, IOException { + + String connectionUrl = "jdbc:tc:cratedb:5.2://localhost/doc?user=crate&TC_DAEMON=true"; + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example query. + Application app = new Application(connectionUrl); + app.querySummitsTable(); + + } + + /** + * Launch container with CrateDB 5.2, using "Reusable Containers (Experimental)". + * + * https://www.testcontainers.org/features/reuse/ + */ + @Test + public void testReadSummitsCrateDBWithReuse() throws SQLException, IOException { + + String connectionUrl = "jdbc:tc:cratedb:5.2://localhost/doc?user=crate&TC_REUSABLE=true"; + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example query. + Application app = new Application(connectionUrl); + app.querySummitsTable(); + + } + +} diff --git a/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestManual.java b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestManual.java new file mode 100644 index 00000000..dbe96165 --- /dev/null +++ b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestManual.java @@ -0,0 +1,39 @@ +package io.crate.example.testing; + +import org.junit.Test; +import org.testcontainers.cratedb.CrateDBContainer; +import org.testcontainers.utility.DockerImageName; + +import java.io.IOException; +import java.sql.SQLException; + + +/** + * Function-scoped testcontainer instance with manual setup/teardown. + * + * - https://www.testcontainers.org/test_framework_integration/junit_4/#manually-controlling-container-lifecycle + */ +public class TestManual { + + @Test + public void testReadSummits() throws SQLException, IOException { + + // Run CrateDB nightly. + DockerImageName image = DockerImageName.parse("crate/crate:nightly").asCompatibleSubstituteFor("crate"); + CrateDBContainer cratedb = new CrateDBContainer(image); + cratedb.start(); + + // Get JDBC URL to CrateDB instance. + String connectionUrl = cratedb.getJdbcUrl(); + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example test. + Application app = new Application(connectionUrl); + app.querySummitsTable(); + + // Tear down CrateDB. + cratedb.stop(); + + } + +} diff --git a/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestSharedSingleton.java b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestSharedSingleton.java new file mode 100644 index 00000000..ecf1da77 --- /dev/null +++ b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestSharedSingleton.java @@ -0,0 +1,49 @@ +package io.crate.example.testing; + +import org.junit.Test; +import org.testcontainers.cratedb.CrateDBContainer; +import org.testcontainers.utility.DockerImageName; + +import java.io.IOException; +import java.sql.SQLException; + + +/** + * Testcontainer instance shared across multiple test classes, implemented using the Singleton pattern. + * + * Sometimes it might be useful to define a container that is only started + * once for several test classes. There is no special support for this use + * case provided by the Testcontainers extension. Instead, this can be + * implemented using the Singleton pattern. + * + * https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers + */ +abstract class AbstractContainerBaseTest { + + static final CrateDBContainer cratedb; + + static { + // Run CrateDB nightly. + DockerImageName image = DockerImageName.parse("crate/crate:nightly").asCompatibleSubstituteFor("crate"); + cratedb = new CrateDBContainer(image); + cratedb.start(); + } +} + + +public class TestSharedSingleton extends AbstractContainerBaseTest { + + @Test + public void testReadSummits() throws SQLException, IOException { + + // Get JDBC URL to CrateDB instance. + String connectionUrl = cratedb.getJdbcUrl(); + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example test. + Application app = new Application(connectionUrl); + app.querySummitsTable(); + + } + +} diff --git a/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestSharedSingletonMatrix.java b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestSharedSingletonMatrix.java new file mode 100644 index 00000000..08e3ed18 --- /dev/null +++ b/testing/testcontainers/java/src/test/java/io/crate/example/testing/TestSharedSingletonMatrix.java @@ -0,0 +1,60 @@ +package io.crate.example.testing; + +import org.junit.Test; +import org.testcontainers.cratedb.CrateDBContainer; +import org.testcontainers.utility.DockerImageName; + +import java.io.IOException; +import java.sql.SQLException; + + +/** + * Testcontainer instance honoring the `CRATEDB_VERSION` environment variable, + * suitable for running a test matrix on different versions of CrateDB. + * + * Possible values are: + * - Version numbers: 5, 5.2, 5.2.3 + * - Nightly build: nightly + */ +abstract class AbstractContainerMatrixBaseTest { + + static final CrateDBContainer cratedb; + + static { + + String cratedb_version = System.getenv("CRATEDB_VERSION"); + String fullImageName; + if (cratedb_version == null) { + fullImageName = "crate:latest"; + } else { + if (cratedb_version.equals("nightly")) { + fullImageName = "crate/crate:nightly"; + } else { + fullImageName = String.format("crate:%s", cratedb_version); + } + } + + // Run CrateDB nightly. + DockerImageName image = DockerImageName.parse(fullImageName).asCompatibleSubstituteFor("crate"); + cratedb = new CrateDBContainer(image); + cratedb.start(); + } +} + + +public class TestSharedSingletonMatrix extends AbstractContainerMatrixBaseTest { + + @Test + public void testReadSummits() throws SQLException, IOException { + + // Get JDBC URL to CrateDB instance. + String connectionUrl = cratedb.getJdbcUrl(); + System.out.println(String.format("Connecting to %s", connectionUrl)); + + // Invoke example test. + Application app = new Application(connectionUrl); + app.querySummitsTable(); + + } + +}