diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.github/codeql.yml b/.github/codeql.yml new file mode 100644 index 0000000..b4ae59c --- /dev/null +++ b/.github/codeql.yml @@ -0,0 +1,62 @@ +name: "Code scanning - action" + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + push: + # run workflow when merging to main/master or develop + branches: + - main + - master + - develop + schedule: + - cron: '0 19 * * 0' + +jobs: + CodeQL-Build: + + # CodeQL runs on ubuntu-latest and windows-latest + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f506fa4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + + # Docker + - package-ecosystem: docker + directory: "/" + schedule: + interval: weekly + time: '11:00' + open-pull-requests-limit: 25 + + # GitHub Actions + - package-ecosystem: "github-actions" + directory: ".github/workflows" + schedule: + interval: "monthly" + open-pull-requests-limit: 25 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0c9e334 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,44 @@ +name: KBase Java Test utilities tests + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + push: + # run workflow when merging to main / master / develop + branches: + - main + - develop + +jobs: + + java_test_utilities_tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - java: '8' + - java: '11' + + steps: + - uses: actions/checkout@v4 + + - name: Setup java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{matrix.java}} + + - name: Run tests + run: | + ./gradlew test + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e5b3fb9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright (c) 2014-present The KBase Project and its Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 2f4ca13..21cdb43 100644 --- a/README.md +++ b/README.md @@ -1 +1,51 @@ # Java Test Utilities + +Contains code for use in tests, including exception checkers, methods to add data to +[Auth2](https://github.com/kbase/auth2) testmode, and controllers for 3rd party data stores. + +## Including the library in your build + +See https://jitpack.io/#kbase/java_test_utilities/ for instructions on how to include JitPack built +dependencies in your build. + +## JavaDoc + +JavaDoc is available at +``` +https://javadoc.jitpack.io/com/github/kbase/java_test_utilities//javadoc/ +``` + +For example: + +https://javadoc.jitpack.io/com/github/kbase/java_test_utilities/0.1.0/javadoc/ + +## Adding and releasing code + +* Adding code + * All code additions and updates must be made as pull requests directed at the develop branch. + * All tests must pass and all new code must be covered by tests. + * All new code must be documented appropriately + * Javadoc + * General documentation if appropriate + * Release notes +* Releases + * The main branch is the stable branch. Releases are made from the develop branch to the main + branch. + * Tag the version in git and github. + * Create a github release. + * Check that the javadoc is appropriately built on JitPack. + +## Testing + +``` +./gradlew test +``` + +## TODO + +* Add tests +* Javadoc + +## Original home + +https://github.com/kbase/java_common \ No newline at end of file diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000..79e3260 --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,5 @@ +# Release notes + +## 0.1.0 + +* Initial release. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..44e8cb6 --- /dev/null +++ b/build.gradle @@ -0,0 +1,72 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'java' + id 'jacoco' + id 'maven-publish' +} + +group = 'com.github.kbase' + +repositories { + mavenCentral() +} + +compileJava { + // TODO BUILD remove when we no longer support java 8, use `options.release = 11` if needed + java.sourceCompatibility = JavaVersion.VERSION_1_8 + java.targetCompatibility = JavaVersion.VERSION_1_8 +} + +java { + withSourcesJar() + withJavadocJar() +} + +test { + testLogging { + exceptionFormat = 'full' + showStandardStreams = true + } + finalizedBy jacocoTestReport +} + +jacocoTestReport { + reports { + xml.required = true + csv.required = true + } +} + +javadoc { + failOnError = false // hamcrest 1.1. causes this to fail + options { + // I don't know why this isn't working, but it's not worth spending time on right now + links "https://docs.oracle.com/javase/8/docs/api/" + } +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + } + } +} + +dependencies { + + // using older dependencies to not force upgrades on repos that might not be able to + // handle them. Need to upgrade the services and then upgrade here + implementation 'junit:junit:4.9' + implementation 'org.slf4j:slf4j-api:1.7.7' + implementation 'ch.qos.logback:logback-classic:1.1.2' + implementation 'commons-io:commons-io:2.4' + implementation "com.fasterxml.jackson.core:jackson-databind:2.9.9" + implementation 'org.apache.commons:commons-lang3:3.1' + implementation 'com.google.guava:guava:18.0' + implementation 'org.ini4j:ini4j:0.5.2' + implementation 'com.github.zafarkhaja:java-semver:0.9.0' +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..4ac3234 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,2 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b82aa23 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/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/HEAD/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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + 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 + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +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/settings.gradle b/settings.gradle new file mode 100644 index 0000000..6c52370 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,8 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.7/userguide/multi_project_builds.html in the Gradle documentation. + */ + +rootProject.name = 'java_test_utilities' diff --git a/src/main/java/us/kbase/testutils/Auth2TestMode.java b/src/main/java/us/kbase/testutils/Auth2TestMode.java new file mode 100644 index 0000000..1764372 --- /dev/null +++ b/src/main/java/us/kbase/testutils/Auth2TestMode.java @@ -0,0 +1,121 @@ +package us.kbase.testutils; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.IOUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; + +public class Auth2TestMode { + + public static void createAuthUser( + final URL authURL, + final String userName, + final String displayName) + throws Exception { + final URL target = new URL(authURL.toString() + "/api/V2/testmodeonly/user"); + final HttpURLConnection conn = getPOSTConnection(target); + + final DataOutputStream writer = new DataOutputStream(conn.getOutputStream()); + writer.writeBytes(new ObjectMapper().writeValueAsString(ImmutableMap.of( + "user", userName, + "display", displayName))); + writer.flush(); + writer.close(); + + checkForError(conn); + } + + private static HttpURLConnection getPOSTConnection(final URL target) throws Exception { + return getConnection("POST", target); + } + + private static HttpURLConnection getPUTConnection(final URL target) throws Exception { + return getConnection("PUT", target); + } + + private static HttpURLConnection getConnection(final String verb, final URL target) + throws Exception { + final HttpURLConnection conn = (HttpURLConnection) target.openConnection(); + conn.setRequestMethod(verb); + conn.setRequestProperty("content-type", "application/json"); + conn.setRequestProperty("accept", "application/json"); + conn.setDoOutput(true); + return conn; + } + + private static void checkForError(final HttpURLConnection conn) throws IOException { + final int rescode = conn.getResponseCode(); + if (rescode < 200 || rescode >= 300) { + System.out.println("Response code: " + rescode); + String err = IOUtils.toString(conn.getErrorStream()); + System.out.println(err); + if (err.length() > 200) { + err = err.substring(0, 200); + } + throw new TestException(err); + } + } + + public static String createLoginToken(final URL authURL, String user) throws Exception { + final URL target = new URL(authURL.toString() + "/api/V2/testmodeonly/token"); + final HttpURLConnection conn = getPOSTConnection(target); + + final DataOutputStream writer = new DataOutputStream(conn.getOutputStream()); + writer.writeBytes(new ObjectMapper().writeValueAsString(ImmutableMap.of( + "user", user, + "type", "Login"))); + writer.flush(); + writer.close(); + + checkForError(conn); + final String out = IOUtils.toString(conn.getInputStream()); + @SuppressWarnings("unchecked") + final Map resp = new ObjectMapper().readValue(out, Map.class); + return (String) resp.get("token"); + } + + public static void createCustomRole( + final URL authURL, + final String role, + final String description) + throws Exception { + final URL target = new URL(authURL.toString() + "/api/V2/testmodeonly/customroles"); + final HttpURLConnection conn = getPOSTConnection(target); + + final DataOutputStream writer = new DataOutputStream(conn.getOutputStream()); + writer.writeBytes(new ObjectMapper().writeValueAsString(ImmutableMap.of( + "id", role, + "desc", description))); + writer.flush(); + writer.close(); + + checkForError(conn); + } + + // will zero out standard roles, which don't do much in test mode + public static void setUserRoles( + final URL authURL, + final String user, + final List customRoles) + throws Exception { + final URL target = new URL(authURL.toString() + "/api/V2/testmodeonly/userroles"); + final HttpURLConnection conn = getPUTConnection(target); + + final DataOutputStream writer = new DataOutputStream(conn.getOutputStream()); + writer.writeBytes(new ObjectMapper().writeValueAsString(ImmutableMap.of( + "user", user, + "customroles", customRoles))); + writer.flush(); + writer.close(); + + checkForError(conn); + } + +} diff --git a/src/main/java/us/kbase/testutils/RegexMatcher.java b/src/main/java/us/kbase/testutils/RegexMatcher.java new file mode 100644 index 0000000..63d8f25 --- /dev/null +++ b/src/main/java/us/kbase/testutils/RegexMatcher.java @@ -0,0 +1,25 @@ +package us.kbase.testutils; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +//http://piotrga.wordpress.com/2009/03/27/hamcrest-regex-matcher/ +public class RegexMatcher extends BaseMatcher { + private final String regex; + + public RegexMatcher(String regex){ + this.regex = regex; + } + + public boolean matches(Object o){ + return ((String)o).matches(regex); + } + + public void describeTo(Description description){ + description.appendText("matches regex=" + regex); + } + + public static RegexMatcher matches(String regex){ + return new RegexMatcher(regex); + } +} diff --git a/src/main/java/us/kbase/testutils/TestCommon.java b/src/main/java/us/kbase/testutils/TestCommon.java new file mode 100644 index 0000000..1d8f793 --- /dev/null +++ b/src/main/java/us/kbase/testutils/TestCommon.java @@ -0,0 +1,195 @@ +package us.kbase.testutils; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.lang.reflect.Field; +import java.time.Instant; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.core.AppenderBase; + +public class TestCommon { + + public static final String LONG101; + public static final String LONG1001; + static { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 100; i++) { + sb.append("a"); + } + final String s100 = sb.toString(); + final StringBuilder sb2 = new StringBuilder(); + for (int i = 0; i < 10; i++) { + sb2.append(s100); + } + LONG101 = s100 + "a"; + LONG1001 = sb2.toString() + "a"; + } + + public static void stfuLoggers() { + ((ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory + .getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME)) + .setLevel(ch.qos.logback.classic.Level.OFF); + java.util.logging.Logger.getLogger("com.mongodb") + .setLevel(java.util.logging.Level.OFF); + } + + public static void printJava() { + System.out.println("Java: " + + System.getProperty("java.runtime.version")); + } + + public static void assertExceptionCorrect( + final Throwable got, + final Throwable expected) { + assertThat("incorrect exception. trace:\n" + + ExceptionUtils.getStackTrace(got), + got.getLocalizedMessage(), + is(expected.getLocalizedMessage())); + assertThat("incorrect exception type", got, instanceOf(expected.getClass())); + } + + //http://quirkygba.blogspot.com/2009/11/setting-environment-variables-in-java.html + @SuppressWarnings("unchecked") + public static Map getenv() + throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + Map unmodifiable = System.getenv(); + Class cu = unmodifiable.getClass(); + Field m = cu.getDeclaredField("m"); + m.setAccessible(true); + return (Map) m.get(unmodifiable); + } + + @SafeVarargs + public static Set set(T... objects) { + return new HashSet(Arrays.asList(objects)); + } + + public static Instant inst(final long epoch) { + return Instant.ofEpochMilli(epoch); + } + + public static class LogEvent { + + public final Level level; + public final String message; + public final String className; + public final Throwable ex; + + public LogEvent(final Level level, final String message, final Class clazz) { + this.level = level; + this.message = message; + this.className = clazz.getName(); + ex = null; + } + + public LogEvent(final Level level, final String message, final String className) { + this.level = level; + this.message = message; + this.className = className; + ex = null; + } + + public LogEvent( + final Level level, + final String message, + final Class clazz, + final Throwable ex) { + this.level = level; + this.message = message; + this.className = clazz.getName(); + this.ex = ex; + } + + public LogEvent( + final Level level, + final String message, + final String className, + final Throwable ex) { + this.level = level; + this.message = message; + this.className = className; + this.ex = ex; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("LogEvent [level="); + builder.append(level); + builder.append(", message="); + builder.append(message); + builder.append(", className="); + builder.append(className); + builder.append(", ex="); + builder.append(ex); + builder.append("]"); + return builder.toString(); + } + } + + public static List setUpSLF4JTestLoggerAppender(final String package_) { + final Logger authRootLogger = (Logger) LoggerFactory.getLogger(package_); + authRootLogger.setAdditive(false); + authRootLogger.setLevel(Level.ALL); + final List logEvents = new LinkedList<>(); + final AppenderBase appender = + new AppenderBase() { + @Override + protected void append(final ILoggingEvent event) { + logEvents.add(event); + } + }; + appender.start(); + authRootLogger.addAppender(appender); + return logEvents; + } + + public static void assertLogEventsCorrect( + final List logEvents, + final LogEvent... expectedlogEvents) { + + assertThat("incorrect log event count for list: " + logEvents, logEvents.size(), + is(expectedlogEvents.length)); + final Iterator iter = logEvents.iterator(); + for (final LogEvent le: expectedlogEvents) { + final ILoggingEvent e = iter.next(); + assertThat("incorrect log level", e.getLevel(), is(le.level)); + assertThat("incorrect originating class", e.getLoggerName(), is(le.className)); + assertThat("incorrect message", e.getFormattedMessage(), is(le.message)); + final IThrowableProxy err = e.getThrowableProxy(); + if (err != null) { + if (le.ex == null) { + fail(String.format("Logged exception where none was expected: %s %s %s", + err.getClassName(), err.getMessage(), le)); + } else { + assertThat("incorrect error class for event " + le, err.getClassName(), + is(le.ex.getClass().getName())); + assertThat("incorrect error message for event " + le, err.getMessage(), + is(le.ex.getMessage())); + } + } else if (le.ex != null) { + fail("Expected exception but none was logged: " + le); + } + } + } + + +} diff --git a/src/main/java/us/kbase/testutils/TestException.java b/src/main/java/us/kbase/testutils/TestException.java new file mode 100644 index 0000000..0f4264b --- /dev/null +++ b/src/main/java/us/kbase/testutils/TestException.java @@ -0,0 +1,16 @@ +package us.kbase.testutils; + +/** + * Thrown when an exception occurs while setting up tests. + * @author gaprice@lbl.gov + * + */ +public class TestException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public TestException() { super(); } + public TestException(String message) { super(message); } + public TestException(String message, Throwable cause) { super(message, cause); } + public TestException(Throwable cause) { super(cause); } +} diff --git a/src/main/java/us/kbase/testutils/controllers/ControllerCommon.java b/src/main/java/us/kbase/testutils/controllers/ControllerCommon.java new file mode 100644 index 0000000..efa4347 --- /dev/null +++ b/src/main/java/us/kbase/testutils/controllers/ControllerCommon.java @@ -0,0 +1,104 @@ +package us.kbase.testutils.controllers; + +import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.List; +import java.util.Set; + +import us.kbase.testutils.TestException; + + +public class ControllerCommon { + + /** See https://gist.github.com/vorburger/3429822 + * Returns a free port number on localhost. + * + * Heavily inspired from org.eclipse.jdt.launching.SocketUtil (to avoid a + * dependency to JDT just because of this). + * Slightly improved with close() missing in JDT. And throws exception + * instead of returning -1. + * + * @return a free port number on localhost + * @throws IllegalStateException if unable to find a free port + */ + public static int findFreePort() { + ServerSocket socket = null; + try { + socket = new ServerSocket(0); + socket.setReuseAddress(true); + int port = socket.getLocalPort(); + try { + socket.close(); + } catch (IOException e) { + // Ignore IOException on close() + } + return port; + } catch (IOException e) { + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + } + } + } + throw new IllegalStateException( + "Could not find a free TCP/IP port"); + } + + public static void checkExe(String exe, String exeType) { + File e = checkFile(exe, exeType, true); + if (!e.canExecute()) { + throw new IllegalArgumentException("The provided " + exeType + + " executable is not executable:" + exe); + } + } + + public static File checkFile(String exe, String exeType) { + return checkFile(exe, exeType, false); + } + + public static File checkFile(String exe, String exeType, + boolean executable) { + + if (exe == null || exe.isEmpty()) { + throw new TestException( + (executable ? "Executable path for " : "Path for ") + + exeType + " cannot be null or the empty string "); + } + File e = new File(exe); + if (!e.exists()) { + throw new IllegalArgumentException("The provided " + exeType + + (executable ? " executable does not exist:" : + " path does not exist: ") + exe); + } + if (!e.isFile()) { + throw new IllegalArgumentException("The provided " + exeType + + (executable ? " executable is not a file:" : + " path is not a file: ") + exe); + } + return e; + } + + public static Path makeTempDirs(Path rootTempDir, String prefix, + List subdirs) + throws IOException { + Files.createDirectories(rootTempDir.toAbsolutePath()); + Set perms = + PosixFilePermissions.fromString("rwx------"); + FileAttribute> attr = + PosixFilePermissions.asFileAttribute(perms); + Path tempDir = Files.createTempDirectory(rootTempDir, prefix, attr); + for(String p: subdirs) { + Files.createDirectories(tempDir.resolve(p)); + } + //TODO toAbsolutePath(); + return tempDir; + } +} diff --git a/src/main/java/us/kbase/testutils/controllers/blobstore/BlobstoreController.java b/src/main/java/us/kbase/testutils/controllers/blobstore/BlobstoreController.java new file mode 100644 index 0000000..5be2380 --- /dev/null +++ b/src/main/java/us/kbase/testutils/controllers/blobstore/BlobstoreController.java @@ -0,0 +1,162 @@ +package us.kbase.testutils.controllers.blobstore; + +import static us.kbase.testutils.controllers.ControllerCommon.checkExe; +import static us.kbase.testutils.controllers.ControllerCommon.findFreePort; +import static us.kbase.testutils.controllers.ControllerCommon.makeTempDirs; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.ini4j.Ini; +import org.ini4j.Profile.Section; + +/** Q&D Utility to run a Blobstore server for the purposes of testing from + * Java. + * @author gaprice@lbl.gov + * + */ +public class BlobstoreController { + + private static final String BLOBSTORE_CFG_SECTION = "BlobStore"; + private final Process blobstore; + private final int port; + private final Path tempDir; + private final Path logfile; + + /** Create the controller. + * @param blobstoreExe the path to the Blobstore executable. + * @param rootTempDir a temporary directory in which to store blobstore data and log + * files. The files will be stored inside a child directory that is unique per invocation. + * @param mongohost the mongo host string, e.g. localhost:27017 + * @param mongoDBname the name of the mongo database to use for storing blobstore data. + * @param s3Host the s3 host string, e.g. localhost:9000 + * @param s3Bucket the s3 bucket to use for storing blobstore data. + * @param s3AccessKey the s3 access key. + * @param s3AccessSecret the s3 access secret. + * @param s3Region the s3 region, e.g. us-west-1 + * @param kbaseAuthURL the root URL of the KBase auth service. + * @param adminRoles the KBase auth service admin roles that the blobstore should recognize + * as denoting Blobstore administrative users. + * @throws Exception if anything goes wrong. + */ + public BlobstoreController( + final String blobstoreExe, + final Path rootTempDir, + final String mongohost, + final String mongoDBname, + final String s3Host, + final String s3Bucket, + final String s3AccessKey, + final String s3AccessSecret, + final String s3Region, + final URL kbaseAuthURL, + final List adminRoles) + throws Exception { + checkExe(blobstoreExe, "blobstore"); + this.tempDir = makeTempDirs(rootTempDir, "BlobstoreController-", Collections.emptyList()); + this.port = findFreePort(); + + final File bsIniFile = createBlobstoreDeployCfg( + mongohost, + mongoDBname, + s3Host, + s3Bucket, + s3AccessKey, + s3AccessSecret, + s3Region, + kbaseAuthURL, + adminRoles + ); + + this.logfile = tempDir.resolve("blobstore.log"); + + ProcessBuilder servpb = new ProcessBuilder(blobstoreExe, "--conf", bsIniFile.toString()) + .redirectErrorStream(true) + .redirectOutput(logfile.toFile()); + + this.blobstore = servpb.start(); + Thread.sleep(1000); //wait for server to start + } + + private File createBlobstoreDeployCfg( + final String mongohost, + final String mongoDBname, + final String s3Host, + final String s3Bucket, + final String s3AccessKey, + final String s3AccessSecret, + final String s3Region, + final URL kbaseAuthURL, + final List adminRoles) + throws IOException { + final File iniFile = tempDir.resolve("blobstore.cfg").toFile(); + final Ini ini = new Ini(); + final Section ss = ini.add(BLOBSTORE_CFG_SECTION); + ss.add("host", "localhost:" + port); + + ss.add("kbase-auth-url", kbaseAuthURL.toString()); + ss.add("kbase-auth-admin-roles", String.join(", ", adminRoles)); + + ss.add("mongodb-host", mongohost); + ss.add("mongodb-database", mongoDBname); + + ss.add("s3-host", s3Host); + ss.add("s3-bucket", s3Bucket); + ss.add("s3-access-key", s3AccessKey); + ss.add("s3-access-secret", s3AccessSecret); + ss.add("s3-region", s3Region); + ss.add("s3-disable-ssl", "true"); + + ini.store(iniFile); + return iniFile; + } + + /** Get the blobstore port. + * @return the port. + */ + public int getPort() { + return port; + } + + /** Get the directory in which the blobstore is storing temporary files. + * @return the temporary directory. + */ + public Path getTempDir() { + return tempDir; + } + + /** Shut down the blob store. + * @param deleteTempFiles true to delete any temporary files. + * @throws IOException if an IO error occurs. + */ + public void destroy(final boolean deleteTempFiles) throws IOException { + destroy(deleteTempFiles, false); + } + + /** Shut down the blob store. + * @param deleteTempFiles true to delete any temporary files. + * @param dumpLogToStdOut print any blobstore logs to standard out. + * @throws IOException if an IO error occurs. + */ + public void destroy(final boolean deleteTempFiles, final boolean dumpLogToStdOut) + throws IOException { + if (blobstore != null) { + blobstore.destroy(); + } + if (dumpLogToStdOut) { + try (final BufferedReader is = Files.newBufferedReader(logfile)) { + is.lines().forEach(l -> System.out.println(l)); + } + } + if (tempDir != null && deleteTempFiles) { + FileUtils.deleteDirectory(tempDir.toFile()); + } + } +} \ No newline at end of file diff --git a/src/main/java/us/kbase/testutils/controllers/minio/MinioController.java b/src/main/java/us/kbase/testutils/controllers/minio/MinioController.java new file mode 100644 index 0000000..84ad39a --- /dev/null +++ b/src/main/java/us/kbase/testutils/controllers/minio/MinioController.java @@ -0,0 +1,98 @@ +package us.kbase.testutils.controllers.minio; + +import static us.kbase.testutils.controllers.ControllerCommon.checkExe; +import static us.kbase.testutils.controllers.ControllerCommon.findFreePort; +import static us.kbase.testutils.controllers.ControllerCommon.makeTempDirs; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Scanner; + +import org.apache.commons.io.FileUtils; + +/** Q&D Utility to run a Minio server for the purposes of testing from + * Java. Always run with the --compat flag. + * @author gaprice@lbl.gov + * + */ +public class MinioController { + + private final Path tempDir; + + private final Process minio; + private final int port; + private final Path logfile; + + public MinioController( + final String minioExe, + final String s3AccessKey, + final String s3AccessSecret, + final Path rootTempDir) + throws Exception { + tempDir = makeTempDirs(rootTempDir, "MinioController-", Arrays.asList("data")); + port = findFreePort(); + + checkExe(minioExe, "minio server"); + + logfile = tempDir.resolve("minio_server.log"); + ProcessBuilder servpb = new ProcessBuilder( + minioExe, + "server", + "--compat", + "--address", "localhost:" + port, + tempDir.resolve("data").toString()) + .redirectErrorStream(true) + .redirectOutput(logfile.toFile()); + + servpb.environment().put("MINIO_ACCESS_KEY", s3AccessKey); + servpb.environment().put("MINIO_SECRET_KEY", s3AccessSecret); + minio = servpb.start(); + Thread.sleep(1000); //wait for server to start + } + + public int getServerPort() { + return port; + } + + public Path getTempDir() { + return tempDir; + } + + public void destroy(boolean deleteTempFiles) throws IOException { + destroy(deleteTempFiles, false); + } + + public void destroy(boolean deleteTempFiles, boolean dumpLogToStdOut) throws IOException { + if (minio != null) { + minio.destroy(); + } + if (dumpLogToStdOut) { + try (final BufferedReader is = Files.newBufferedReader(logfile)) { + is.lines().forEach(l -> System.out.println(l)); + } + } + if (tempDir != null && deleteTempFiles) { + FileUtils.deleteDirectory(tempDir.toFile()); + } + } + + public static void main(String[] args) throws Exception { + MinioController ac = new MinioController( + "Minio", + "foobar", + "Wheewhoowhump", + Paths.get("minio_temp_dir")); + System.out.println(ac.getServerPort()); + Scanner reader = new Scanner(System.in); + System.out.println("any char to shut down"); + //get user input for a + reader.next(); + ac.destroy(false); + reader.close(); + } + +} diff --git a/src/main/java/us/kbase/testutils/controllers/mongo/MongoController.java b/src/main/java/us/kbase/testutils/controllers/mongo/MongoController.java new file mode 100644 index 0000000..af8fb5d --- /dev/null +++ b/src/main/java/us/kbase/testutils/controllers/mongo/MongoController.java @@ -0,0 +1,156 @@ +package us.kbase.testutils.controllers.mongo; + +import static us.kbase.testutils.controllers.ControllerCommon.checkExe; +import static us.kbase.testutils.controllers.ControllerCommon.findFreePort; +import static us.kbase.testutils.controllers.ControllerCommon.makeTempDirs; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; + +import com.github.zafarkhaja.semver.Version; +import org.apache.commons.io.FileUtils; + + +/** Q&D Utility to run a Mongo server for the purposes of testing from + * Java. + * @author gaprice@lbl.gov, sijiex@lbl.gov + * + */ +public class MongoController { + + private final static String DATA_DIR = "data"; + + private final static List tempDirectories = + new LinkedList(); + static { + tempDirectories.add(DATA_DIR); + } + + private final static Version MONGO_DB_6_1 = + Version.forIntegers(6,1); + + private final Path tempDir; + + private final Process mongo; + + private final int port; + + public MongoController( + final String mongoExe, + final Path rootTempDir) + throws Exception { + this(mongoExe, rootTempDir, false); + } + + public MongoController( + final String mongoExe, + final Path rootTempDir, + final boolean useWiredTiger) + throws Exception { + checkExe(mongoExe, "mongod server"); + tempDir = makeTempDirs(rootTempDir, "MongoController-", tempDirectories); + port = findFreePort(); + Version dbVer = getMongoDBVer(mongoExe); + List command = getMongoServerStartCommand(mongoExe, useWiredTiger, dbVer); + mongo = startProcess(command); + } + + public int getServerPort() { + return port; + } + + public Path getTempDir() { + return tempDir; + } + + public void destroy(boolean deleteTempFiles) throws IOException, InterruptedException { + if (mongo != null) { + mongo.destroy(); + } + if (tempDir != null && deleteTempFiles) { + try { + FileUtils.deleteDirectory(tempDir.toFile()); + } catch (IOException e) { + // probably mongo deleted a file after the function listed it, race condition + Thread.sleep(1000); + // if it fails again just fail hard + FileUtils.deleteDirectory(tempDir.toFile()); + } + } + } + + private static Version getMongoDBVer(final String mongoExe) throws IOException { + + // build MongoDB version check command + List command = new LinkedList(); + command.addAll(Arrays.asList(mongoExe, "--version")); + + // start MongoDB version check process + ProcessBuilder checkVerPb = new ProcessBuilder(command); + Process checkVerProcess = checkVerPb.start(); + + // parse mongod --version output string + String dbVer = new BufferedReader( + new InputStreamReader(checkVerProcess.getInputStream())) + .lines() + .collect(Collectors.joining(" ")) + .split(" ")[2].substring(1); + + System.out.println("MongoDB version: " + dbVer); + checkVerProcess.destroy(); + return Version.valueOf(dbVer); + } + + private List getMongoServerStartCommand(final String mongoExe, + final boolean useWiredTiger, + final Version dbVer) { + List command = new LinkedList(); + command.addAll(Arrays.asList(mongoExe, "--port", "" + port, + "--dbpath", tempDir.resolve(DATA_DIR).toString())); + + // Starting in MongoDB 6.1, journaling is always enabled. + // As a result, MongoDB removes the storage.journal.enabled option + // and the corresponding --journal and --nojournal command-line options. + // https://www.mongodb.com/docs/manual/release-notes/6.1/#changes-to-journaling + if (dbVer.lessThan(MONGO_DB_6_1)) { + command.addAll(Arrays.asList("--nojournal")); + } + if (useWiredTiger) { + command.addAll(Arrays.asList("--storageEngine", "wiredTiger")); + } + return command; + } + + private Process startProcess(List command) throws Exception { + ProcessBuilder servpb = new ProcessBuilder(command) + .redirectErrorStream(true) + .redirectOutput(getTempDir().resolve("mongo.log").toFile()); + + Process mongoProcess = servpb.start(); + Thread.sleep(1000); //wait for server to start up + return mongoProcess; + } + + public static void main(String[] args) throws Exception { + MongoController ac = new MongoController( + "/home/crushingismybusiness/mongo/3.6.12/bin/mongod", + Paths.get("workspacetesttemp")); + System.out.println(ac.getServerPort()); + System.out.println(ac.getTempDir()); + Scanner reader = new Scanner(System.in); + System.out.println("any char to shut down"); + //get user input for a + reader.next(); + ac.destroy(true); + reader.close(); + } + +} diff --git a/src/test/java/us/kbase/test/testutils/Auth2TestModeTest.java b/src/test/java/us/kbase/test/testutils/Auth2TestModeTest.java new file mode 100644 index 0000000..8d6737c --- /dev/null +++ b/src/test/java/us/kbase/test/testutils/Auth2TestModeTest.java @@ -0,0 +1,17 @@ +package us.kbase.test.testutils; + +import org.junit.Test; + +import us.kbase.testutils.Auth2TestMode; + +public class Auth2TestModeTest { + + // TODO TEST + + @Test + public void noop() throws Exception { + @SuppressWarnings("unused") + final Class foo = Auth2TestMode.class; + } + +} diff --git a/src/test/java/us/kbase/test/testutils/RegexMatcherTest.java b/src/test/java/us/kbase/test/testutils/RegexMatcherTest.java new file mode 100644 index 0000000..acbcc0d --- /dev/null +++ b/src/test/java/us/kbase/test/testutils/RegexMatcherTest.java @@ -0,0 +1,17 @@ +package us.kbase.test.testutils; + +import org.junit.Test; + +import us.kbase.testutils.RegexMatcher; + +public class RegexMatcherTest { + + // TODO TEST + + @Test + public void noop() throws Exception { + @SuppressWarnings("unused") + final Class foo = RegexMatcher.class; + } + +} diff --git a/src/test/java/us/kbase/test/testutils/TestCommonTest.java b/src/test/java/us/kbase/test/testutils/TestCommonTest.java new file mode 100644 index 0000000..a08493d --- /dev/null +++ b/src/test/java/us/kbase/test/testutils/TestCommonTest.java @@ -0,0 +1,17 @@ +package us.kbase.test.testutils; + +import org.junit.Test; + +import us.kbase.testutils.TestCommon; + +public class TestCommonTest { + + // TODO TEST + + @Test + public void noop() throws Exception { + @SuppressWarnings("unused") + final Class foo = TestCommon.class; + } + +} diff --git a/src/test/java/us/kbase/test/testutils/controllers/ControllerCommonTest.java b/src/test/java/us/kbase/test/testutils/controllers/ControllerCommonTest.java new file mode 100644 index 0000000..7a1ba7a --- /dev/null +++ b/src/test/java/us/kbase/test/testutils/controllers/ControllerCommonTest.java @@ -0,0 +1,17 @@ +package us.kbase.test.testutils.controllers; + +import org.junit.Test; + +import us.kbase.testutils.controllers.ControllerCommon; + +public class ControllerCommonTest { + + // TODO TEST + + @Test + public void noop() throws Exception { + @SuppressWarnings("unused") + final Class foo = ControllerCommon.class; + } + +} diff --git a/src/test/java/us/kbase/test/testutils/controllers/blobstore/BlobstoreControllerTest.java b/src/test/java/us/kbase/test/testutils/controllers/blobstore/BlobstoreControllerTest.java new file mode 100644 index 0000000..9e4e4b9 --- /dev/null +++ b/src/test/java/us/kbase/test/testutils/controllers/blobstore/BlobstoreControllerTest.java @@ -0,0 +1,17 @@ +package us.kbase.test.testutils.controllers.blobstore; + +import org.junit.Test; + +import us.kbase.testutils.controllers.blobstore.BlobstoreController; + +public class BlobstoreControllerTest { + + // TODO TEST + + @Test + public void noop() throws Exception { + @SuppressWarnings("unused") + final Class foo = BlobstoreController.class; + } + +} diff --git a/src/test/java/us/kbase/test/testutils/controllers/minio/MinioControllerTest.java b/src/test/java/us/kbase/test/testutils/controllers/minio/MinioControllerTest.java new file mode 100644 index 0000000..ed8deeb --- /dev/null +++ b/src/test/java/us/kbase/test/testutils/controllers/minio/MinioControllerTest.java @@ -0,0 +1,17 @@ +package us.kbase.test.testutils.controllers.minio; + +import org.junit.Test; + +import us.kbase.testutils.controllers.minio.MinioController; + +public class MinioControllerTest { + + // TODO TEST + + @Test + public void noop() throws Exception { + @SuppressWarnings("unused") + final Class foo = MinioController.class; + } + +} diff --git a/src/test/java/us/kbase/test/testutils/controllers/mongo/MongoControllerTest.java b/src/test/java/us/kbase/test/testutils/controllers/mongo/MongoControllerTest.java new file mode 100644 index 0000000..b8576eb --- /dev/null +++ b/src/test/java/us/kbase/test/testutils/controllers/mongo/MongoControllerTest.java @@ -0,0 +1,17 @@ +package us.kbase.test.testutils.controllers.mongo; + +import org.junit.Test; + +import us.kbase.testutils.controllers.mongo.MongoController; + +public class MongoControllerTest { + + // TODO TEST + + @Test + public void noop() throws Exception { + @SuppressWarnings("unused") + final Class foo = MongoController.class; + } + +}