diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6dade98 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,80 @@ +name: Build & test +on: + push: + branches-ignore: [ main ] + pull_request: + branches: [ develop ] + workflow_dispatch: + repository_dispatch: + types: [ utPLSQL-build ] + +defaults: + run: + shell: bash + +jobs: + build: + + runs-on: ubuntu-latest + + services: + oracle: + image: gvenzl/oracle-xe:21-slim + env: + ORACLE_PASSWORD: oracle + ports: + - 1521:1521 + options: >- + --health-cmd healthcheck.sh + --health-interval 10s + --health-timeout 5s + --health-retries 10 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Install utPLSQL + run: sh ${{ github.workspace }}/scripts/1_install_utplsql.sh + + - name: Install demo project + run: sh ${{ github.workspace }}/scripts/2_install_demo_project.sh + + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Maven unit and integration tests + run: mvn clean verify sonar:sonar -Pcoverage -Dsonar.projectKey=utPLSQL_utPLSQL-java-api + env: + GITHUB_TOKEN: ${{ secrets.API_TOKEN_GITHUB }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: Maven deploy snapshot + run: mvn deploy -DskipTests + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + + - name: Publish unit test results + uses: EnricoMi/publish-unit-test-result-action@v1.24 + if: always() + with: + files: target/**/TEST**.xml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9cee803 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +name: Build and deploy release + +on: + push: + branches: [ main ] + +defaults: + run: + shell: bash + +jobs: + build: + + runs-on: ubuntu-latest + + services: + oracle: + image: gvenzl/oracle-xe:21-slim + env: + ORACLE_PASSWORD: oracle + ports: + - 1521:1521 + options: >- + --health-cmd healthcheck.sh + --health-interval 10s + --health-timeout 5s + --health-retries 10 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Install utPLSQL + run: sh ${{ github.workspace }}/scripts/1_install_utplsql.sh + + - name: Install demo project + run: sh ${{ github.workspace }}/scripts/2_install_demo_project.sh + + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Maven deploy release + run: mvn clean deploy -Prelease + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + + - name: Publish unit test results + uses: EnricoMi/publish-unit-test-result-action@v1.24 + if: always() + with: + files: target/**/TEST**.xml diff --git a/.gitignore b/.gitignore index 2d0c11c..9ec7c9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,14 @@ # IntelliJ *.iml -.idea/ + +.idea +!/.idea/codeStyles +!/.idea/dictionaries # Maven target/ +build +.gradle pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup @@ -15,3 +20,8 @@ buildNumber.properties # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) !/.mvn/wrapper/maven-wrapper.jar + +#Exclude CoverageHTMLReporter resources as they are managed by maven +src/main/resources/CoverageHTMLReporter/ +/gradle.properties + diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..cb28b0e Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c257d25 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e32506e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,72 +0,0 @@ -sudo: required -language: java - -services: - - docker - -jdk: - - oraclejdk8 - -env: - global: - - DOCKER_CFG=$HOME/.docker - - DOCKER_REPO="utplsqlv3/oracledb" - - CACHE_DIR=$HOME/.cache - - MAVEN_HOME=/usr/local/maven - - MAVEN_CFG=$HOME/.m2 - - DB_URL="127.0.0.1:1521:XE" - - DB_USER=app - - DB_PASS=app - - ORACLE_VERSION="11g-r2-xe" - - DOCKER_OPTIONS="--shm-size=1g" - - UTPLSQL_FILE="utPLSQL" - matrix: - - UTPLSQL_VERSION="v3.0.0" - UTPLSQL_FILE="utPLSQLv3.0.0" - - UTPLSQL_VERSION="v3.0.1" - - UTPLSQL_VERSION="v3.0.2" - - UTPLSQL_VERSION="v3.0.3" - - UTPLSQL_VERSION="v3.0.4" - -cache: - directories: - - $DOCKER_CFG - - $CACHE_DIR - - $MAVEN_CFG - -install: - - bash .travis/maven_cfg.sh - - bash .travis/start_db.sh - - bash .travis/install_utplsql.sh - - bash .travis/install_demo_project.sh - -before_script: - - cp .travis/settings.xml $MAVEN_CFG/settings.xml - -script: - - mvn test -B - -before_deploy: - - if [ ! -z "$TRAVIS_TAG" ]; then VERSION=$(tr -d "/v/" <<<$TRAVIS_TAG); mvn org.codehaus.mojo:versions-maven-plugin:2.1:set -DnewVersion=${VERSION}; fi - -deploy: - - provider: script - script: mvn clean deploy -DskipTests=true -B -U - skip_cleanup: true - on: - repository: utPLSQL/utPLSQL-java-api - tags: true - # Use only first job "#xxx.1" to publish artifacts - condition: "${TRAVIS_JOB_NUMBER} =~ \\.1$" - - - provider: script - script: mvn clean deploy -DskipTests=true -B -U - skip_cleanup: true - on: - repository: utPLSQL/utPLSQL-java-api - branch: develop - # Use only first job "#xxx.1" to publish artifacts - condition: "${TRAVIS_JOB_NUMBER} =~ \\.1$" - -notifications: - slack: $SLACK_API_TOKEN diff --git a/.travis/create_api_user.sh b/.travis/create_api_user.sh deleted file mode 100644 index c898f15..0000000 --- a/.travis/create_api_user.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -ev - -sqlplus -S -L sys/oracle@//127.0.0.1:1521/xe AS SYSDBA < demo_project.sh.tmp < install.sh.tmp < - - - - - - - - maven.oracle.com - ###USERNAME### - ###PASSWORD### - - - ANY - ANY - OAM 11g - - - - - - http.protocol.allow-circular-redirects - %b,true - - - - - - - - - diff --git a/.travis/settings.xml b/.travis/settings.xml deleted file mode 100644 index 8c1bb7f..0000000 --- a/.travis/settings.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - maven.oracle.com - ${env.ORACLE_OTN_USER} - ${env.ORACLE_OTN_PASSWORD} - - - ANY - ANY - OAM 11g - - - - - - http.protocol.allow-circular-redirects - %b,true - - - - - - - - packagecloud-utPLSQL - ${env.PACKAGECLOUD_TOKEN} - - - - diff --git a/.travis/start_db.sh b/.travis/start_db.sh deleted file mode 100644 index 2367d64..0000000 --- a/.travis/start_db.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -set -ev - -# If docker credentials are not cached, do the login. -if [ ! -f $DOCKER_CFG/config.json ]; then - docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD" -else - echo "Using docker login from cache..." -fi - -# Pull the specified db version from docker hub. -docker pull $DOCKER_REPO:$ORACLE_VERSION -docker run -d --name $ORACLE_VERSION $DOCKER_OPTIONS -p 1521:1521 $DOCKER_REPO:$ORACLE_VERSION -docker logs -f $ORACLE_VERSION | grep -m 1 "DATABASE IS READY TO USE!" --line-buffered diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..adc8b91 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing +*Don't forget to configure your JAVA_HOME environment variable.* + +### Local database with utPLSQL and utPLSQL-demo-project + +To usefully contribute you'll have to set up a local database with installed [latest utPLSQL v3](https://github.com/utPLSQL/utPLSQL) and [utPLSQL-demo-project](https://github.com/utPLSQL/utPLSQL-demo-project). +The demo-project will serve as your test user. See .travis.yml to see an example on how it can be installed. +By default, tests are executed against `app/app` user of `localhost:1521/XE database`. + +If you want to run tests against another database you may set `DB_URL`, `DB_USER`, `DB_PASS` environment variables. + +When you have local database set up you can run the complete build including integration tests by executing +```bash +./mvnw verify +``` + +### Skip the local database part + +If you want to skip the local database part, just run +```bash +./mvnw test +``` diff --git a/README.md b/README.md index 8903a31..3f11520 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,38 @@ -[![Build Status](https://img.shields.io/travis/utPLSQL/utPLSQL-java-api/develop.svg?label=develop-branch)](https://travis-ci.org/utPLSQL/utPLSQL-java-api) -[![Build Status](https://img.shields.io/travis/utPLSQL/utPLSQL-java-api/master.svg?label=master-branch)](https://travis-ci.org/utPLSQL/utPLSQL-java-api) +[![Build status](https://github.com/utPLSQL/utPLSQL-java-api/actions/workflows/build.yml/badge.svg)](https://github.com/utPLSQL/utPLSQL-java-api/actions/workflows/build.yml) # utPLSQL-java-api -This is a collection of classes, that makes easy to access the [utPLSQL v3](https://github.com/utPLSQL/utPLSQL/) database objects using Java. +This is a collection of classes, that makes it easy to access the [utPLSQL v3](https://github.com/utPLSQL/utPLSQL/) database objects using Java. * Uses [ut_runner.run](https://github.com/utPLSQL/utPLSQL/blob/develop/docs/userguide/running-unit-tests.md#ut_runnerrun-procedures) methods to execute tests. * Can gather results asynchronously from multiple reporters. +* Handles compatibility for all 3.x versions of utPLSQL ## Downloading -This is a Maven Library project, you can add on your Java project as a dependency. - -The library is hosted on ![[packagecloud](https://packagecloud.io/utPLSQL/utPLSQL-java-api)](https://packagecloud.io/images/packagecloud-badge.png) +This is a Maven Library project, you can add on your Java project as a dependency. -You install this Maven repository by adding it to the section of your pom.xml. No special plugins or extensions are required. +*Notice: You no longer need to configure an additional repository. The library is available in Maven Central since version 3.1.15.* -```xml - - - utplsql-java-api - - https://packagecloud.io/utplsql/utplsql-java-api/maven2 - - - true - - - true - - - -``` - -To use the java-api library, add this to the `` section of your `pom.xml`. ```xml org.utplsql - java-api - 3.0.4 - compile + utplsql-java-api + 3.1.16 ``` ## Compatibility The latest Java-API is always compatible with all database frameworks of the same major version. -For example API-3.0.4 is compatible with database framework 3.0.0-3.0.4 but not with database framework 2.x. +For example API-3.0.4 is compatible with database framework 3.0.0-3.1.* but not with database framework 2.x. + +It is although recommended to always use the latest release of the API to build your tools for utPLSQL. ## Usage +You can find examples for many features of the java-api in the Unit- and Integration tests. + +### Test-Runner + Executing tests using default parameters: ```java try (Connection conn = DriverManager.getConnection(url)) { @@ -66,35 +51,59 @@ try (Connection conn = DriverManager.getConnection(url)) { .addReporter(documentationReporter) .run(conn); - new OutputBuffer(documentationReporter) + documentationReporter + .getOutputBuffer() + .setFetchSize(1) .printAvailable(conn, System.out); } catch (SQLException e) { e.printStackTrace(); } ``` -## Contributing -To develop it locally, you need to setup your maven environment. +### Optional Features -### Maven Installation -That's the easy part, you just need to download the Maven binaries and extract it somewhere, then put the maven/bin folder on your PATH. +There might be some features which are not available in previous versions of utPLSQL. +These "optional features" are listed in the enum org.utplsql.api.compatibility.OptionalFeatures +and their availability can be checked against a connection or Version-object: -https://maven.apache.org/install.html +```OptionalFeatures.CUSTOM_REPORTERS.isAvailableFor( databaseConnection );``` -*Don't forget to configure your JAVA_HOME environment variable.* +### Compatibility-Proxy +To handle downwards-compatibility, java-api provides an easy to use CompatiblityProxy, which is instantiated with a connection. +It will check the database-version of utPLSQL and is used in several other features like `TestRunner` and `ReporterFactory`. +You can also ask it for the database-version. -### Oracle Maven Repository -The library uses OJDBC Driver to connect to the database, it's added as a maven dependency. To be able to download the Oracle dependencies, you need to configure your access to Oracle's Maven Repository: +```java +try (Connection conn = DriverManager.getConnection(url)) { + CompatiblityProxy proxy = new CompatibilityProxy( conn ); + Version version = proxy.getUtPlsqlVersion(); +} catch (SQLException e) { + e.printStackTrace(); +} +``` -http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010 +### Reporter-Factory -*Sections 6.1 and 6.5 are the more important ones, and the only ones you need if you're using the latest Maven version.* +The java-api provides a ReporterFactory you can use to inject your own implementations of (java-side) reporters or reporter-handlers. +It also provides a more generic approach to Reporter-handling. -After configuring your access to Oracle's Maven repository, you will be able to successfully build this API. +If you request the Reporter-Factory for a Reporter it has no specific implementation for it will just +return an instance of a `DefaultReporter` with the given name as SQL-Type, assuming +that it exists in the database. Therefore, you can address custom reporters without the need +of a specific java-side implementation. -```bash -cd utPLSQL-java-api -mvn clean package install -DskipTests +```java +try (Connection conn = DriverManager.getConnection(url)) { + ReporterFactory reporterFactory = ReporterFactory.createDefault( new CompatibilityProxy( conn )); + reporterFactory.registerReporterFactoryMethod(CoreReporters.UT_DOCUMENTATION_REPORTER.name(), MyCustomReporterImplementation::new, "Custom handler for UT_DOCUMENTATION_REPORTER"); + + Reporter reporter = reporterFactory.createReporter(CreateReporters.UT_DOCUMENTATION_REPORTER.name()); +} catch (SQLException e) { + e.printStackTrace(); +} ``` -The cmd above is ignoring unit tests because it needs a database connection with the latest utPLSQL v3 installed. Please take a look on .travis.yml and .travis folder to see how testing was configured. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8d937f4 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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 + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c4586b5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 33fa826..189ef45 100644 --- a/pom.xml +++ b/pom.xml @@ -1,82 +1,225 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - org.utplsql - java-api - 3.0.4-SNAPSHOT - jar + org.utplsql + utplsql-java-api + 3.1.17-SNAPSHOT - utPLSQL-java-api - https://github.com/utPLSQL/utPLSQL-java-api + utPLSQL Java API + Java API for running Unit Tests with utPLSQL v3+. + https://github.com/utPLSQL/utPLSQL-java-api - - UTF-8 - 1.8 - 1.8 - + + UTF-8 + 1.8 + 1.8 + 5.5.2 + 19.3.0.0 - - - com.oracle.jdbc - ojdbc8 - 12.2.0.1 - compile - - - com.oracle.jdbc - orai18n - 12.2.0.1 - compile - - - junit - junit - 4.12 - test - - + utplsql + https://sonarcloud.io + - - - maven.oracle.com - - true - - - false - - https://maven.oracle.com - default - - + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + - - - maven.oracle.com - https://maven.oracle.com - - + + + Simon Martinelli + utPLSQL.org + http://utplsql.org + simon@martineli.ch + https://martinelli.ch + + - - - - io.packagecloud.maven.wagon - maven-packagecloud-wagon - 0.0.6 - - - + + scm:git@github.com:utPLSQL/utPLSQL-java-api.git + https://github.com/utPLSQL/utPLSQL-java-api + - - - packagecloud-utPLSQL - packagecloud+https://packagecloud.io/utPLSQL/utPLSQL-java-api - - - packagecloud-utPLSQL - packagecloud+https://packagecloud.io/utPLSQL/utPLSQL-java-api - - + + + org.slf4j + slf4j-api + 1.7.36 + + + com.oracle.database.jdbc + ojdbc8 + 19.3.0.0 + + + com.oracle.database.nls + orai18n + 19.3.0.0 + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + org.hamcrest + hamcrest + 2.1 + + + org.mockito + mockito-core + 3.0.0 + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + ${dbUrl} + ${dbUser} + ${dbPass} + + + + + com.amashchenko.maven.plugin + gitflow-maven-plugin + 1.18.0 + + true + + main + + + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + + prepare-agent + + prepare-agent + + + + report + + report + + + + + + + + + release + + + release + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.1 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + verify + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + sign-artifacts + verify + + sign + + + + + + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + diff --git a/scripts/0_start_db.sh b/scripts/0_start_db.sh new file mode 100644 index 0000000..d319407 --- /dev/null +++ b/scripts/0_start_db.sh @@ -0,0 +1 @@ +docker run -d --name ora-utplsql -p 1521:1521 -e ORACLE_PASSWORD=oracle gvenzl/oracle-xe:21-slim diff --git a/scripts/1_install_utplsql.sh b/scripts/1_install_utplsql.sh new file mode 100644 index 0000000..bb6577c --- /dev/null +++ b/scripts/1_install_utplsql.sh @@ -0,0 +1,8 @@ +UTPLSQL_DOWNLOAD_URL=$(curl --silent https://api.github.com/repos/utPLSQL/utPLSQL/releases/latest | awk '/browser_download_url/ { print $2 }' | grep ".zip\"" | sed 's/"//g') + +curl -Lk "${UTPLSQL_DOWNLOAD_URL}" -o utPLSQL.zip + +unzip -q utPLSQL.zip + +docker run --rm -v $(pwd)/utPLSQL:/utPLSQL -w /utPLSQL/source --network host \ + --entrypoint sqlplus truemark/sqlplus:19.8 sys/oracle@//127.0.0.1:1521/XE as sysdba @install_headless.sql UT3 UT3 users diff --git a/scripts/2_install_demo_project.sh b/scripts/2_install_demo_project.sh new file mode 100644 index 0000000..a4089b1 --- /dev/null +++ b/scripts/2_install_demo_project.sh @@ -0,0 +1,11 @@ +docker run --rm -v $(pwd):/work -w /work/ --network host --entrypoint sqlplus truemark/sqlplus:19.8 \ + sys/oracle@//127.0.0.1:1521/XE as sysdba @scripts/sql/create_users.sql + +docker run --rm -v $(pwd):/work -w /work/ --network host --entrypoint sqlplus truemark/sqlplus:19.8 \ + app/pass@//127.0.0.1:1521/XE @scripts/sql/create_app_objects.sql + +docker run --rm -v $(pwd):/work -w /work/ --network host --entrypoint sqlplus truemark/sqlplus:19.8 \ + code_owner/pass@//127.0.0.1:1521/XE @scripts/sql/create_source_owner_objects.sql + +docker run --rm -v $(pwd):/work -w /work/ --network host --entrypoint sqlplus truemark/sqlplus:19.8 \ + tests_owner/pass@//127.0.0.1:1521/XE @scripts/sql/create_tests_owner_objects.sql diff --git a/scripts/sql/create_app_objects.sql b/scripts/sql/create_app_objects.sql new file mode 100644 index 0000000..2ad509f --- /dev/null +++ b/scripts/sql/create_app_objects.sql @@ -0,0 +1,9 @@ +whenever sqlerror exit failure rollback +whenever oserror exit failure rollback + +@scripts/sql/simple/scripts/sources/TO_TEST_ME.sql +@scripts/sql/simple/scripts/sources/APP.PKG_TEST_ME.pks +@scripts/sql/simple/scripts/sources/APP.PKG_TEST_ME.pkb + +@scripts/sql/simple/scripts/tests/APP.TEST_PKG_TEST_ME.pks +@scripts/sql/simple/scripts/tests/APP.TEST_PKG_TEST_ME.pkb diff --git a/scripts/sql/create_source_owner_objects.sql b/scripts/sql/create_source_owner_objects.sql new file mode 100644 index 0000000..bc10482 --- /dev/null +++ b/scripts/sql/create_source_owner_objects.sql @@ -0,0 +1,6 @@ +whenever sqlerror exit failure rollback +whenever oserror exit failure rollback + +@scripts/sql/owner_param/scripts/sources/foo/tables/TO_TEST_ME.sql +@scripts/sql/owner_param/scripts/sources/foo/packages/PKG_TEST_ME.pks +@scripts/sql/owner_param/scripts/sources/foo/package_bodies/PKG_TEST_ME.pkb diff --git a/scripts/sql/create_tests_owner_objects.sql b/scripts/sql/create_tests_owner_objects.sql new file mode 100644 index 0000000..cb244db --- /dev/null +++ b/scripts/sql/create_tests_owner_objects.sql @@ -0,0 +1,8 @@ +whenever sqlerror exit failure rollback +whenever oserror exit failure rollback + +create synonym TO_TEST_ME for CODE_OWNER.TO_TEST_ME; +create synonym PKG_TEST_ME for CODE_OWNER.PKG_TEST_ME; + +@scripts/sql/owner_param/scripts/test/bar/packages/TEST_PKG_TEST_ME.pks +@scripts/sql/owner_param/scripts/test/bar/package_bodies/TEST_PKG_TEST_ME.pkb diff --git a/scripts/sql/create_users.sql b/scripts/sql/create_users.sql new file mode 100644 index 0000000..fe64882 --- /dev/null +++ b/scripts/sql/create_users.sql @@ -0,0 +1,30 @@ +whenever sqlerror exit failure rollback +whenever oserror exit failure rollback +set echo off +set verify off + +define UTPLSQL_USER = 'UT3'; +define APP_USER = 'APP'; +define CODE_OWNER = 'CODE_OWNER'; +define TESTS_OWNER = 'TESTS_OWNER'; +define DB_PASS = 'pass'; + +grant execute any procedure to &UTPLSQL_USER; +grant create any procedure to &UTPLSQL_USER; +grant execute on dbms_lob to &UTPLSQL_USER; +grant execute on dbms_sql to &UTPLSQL_USER; +grant execute on dbms_xmlgen to &UTPLSQL_USER; +grant execute on dbms_lock to &UTPLSQL_USER; + +create user &APP_USER identified by &DB_PASS quota unlimited on USERS default tablespace USERS; +grant create session, create procedure, create type, create table, create sequence, create view to &APP_USER; +grant select any dictionary to &APP_USER; + +create user &CODE_OWNER identified by &DB_PASS quota unlimited on USERS default tablespace USERS; +grant create session, create procedure, create type, create table, create sequence, create view to &CODE_OWNER; + +create user &TESTS_OWNER identified by &DB_PASS quota unlimited on USERS default tablespace USERS; +grant create session, create procedure, create type, create table, create sequence, create view, create synonym to &TESTS_OWNER; +grant select any dictionary to &TESTS_OWNER; +grant select any table, delete any table, drop any table to &TESTS_OWNER; +grant execute any procedure to &TESTS_OWNER; diff --git a/scripts/sql/owner_param/scripts/sources/foo/package_bodies/PKG_TEST_ME.pkb b/scripts/sql/owner_param/scripts/sources/foo/package_bodies/PKG_TEST_ME.pkb new file mode 100644 index 0000000..d6846b2 --- /dev/null +++ b/scripts/sql/owner_param/scripts/sources/foo/package_bodies/PKG_TEST_ME.pkb @@ -0,0 +1,27 @@ +CREATE OR REPLACE PACKAGE BODY CODE_OWNER.PKG_TEST_ME IS + -- + -- This + -- + FUNCTION FC_TEST_ME(PPARAM1 IN VARCHAR2) RETURN NUMBER IS + BEGIN + IF PPARAM1 IS NULL THEN + RETURN NULL; + ELSIF PPARAM1 = '1' THEN + RETURN 1; + ELSE + RETURN 0; + END IF; + END FC_TEST_ME; + + PROCEDURE PR_TEST_ME(PSNAME IN VARCHAR2) IS + BEGIN + IF PSNAME IS NULL THEN + NULL; + ELSE + INSERT INTO TO_TEST_ME (SNAME) VALUES (PSNAME); + COMMIT; + END IF; + END PR_TEST_ME; + +END PKG_TEST_ME; +/ diff --git a/scripts/sql/owner_param/scripts/sources/foo/packages/PKG_TEST_ME.pks b/scripts/sql/owner_param/scripts/sources/foo/packages/PKG_TEST_ME.pks new file mode 100644 index 0000000..959b122 --- /dev/null +++ b/scripts/sql/owner_param/scripts/sources/foo/packages/PKG_TEST_ME.pks @@ -0,0 +1,8 @@ +-- +-- This package is used TO demonstrate the utPL/SQL possibilities +-- +CREATE OR REPLACE PACKAGE CODE_OWNER.PKG_TEST_ME AS + FUNCTION FC_TEST_ME(PPARAM1 IN VARCHAR2) RETURN NUMBER; + PROCEDURE PR_TEST_ME(PSNAME IN VARCHAR2); +END PKG_TEST_ME; +/ \ No newline at end of file diff --git a/scripts/sql/owner_param/scripts/sources/foo/tables/TO_TEST_ME.sql b/scripts/sql/owner_param/scripts/sources/foo/tables/TO_TEST_ME.sql new file mode 100644 index 0000000..b2e90d9 --- /dev/null +++ b/scripts/sql/owner_param/scripts/sources/foo/tables/TO_TEST_ME.sql @@ -0,0 +1,8 @@ +-- +-- This is a table used to demonstrate the UNIT test framework. +-- +CREATE TABLE TO_TEST_ME +( + SNAME VARCHAR2(10) +) +/ diff --git a/scripts/sql/owner_param/scripts/test/bar/package_bodies/TEST_PKG_TEST_ME.pkb b/scripts/sql/owner_param/scripts/test/bar/package_bodies/TEST_PKG_TEST_ME.pkb new file mode 100644 index 0000000..1b34f08 --- /dev/null +++ b/scripts/sql/owner_param/scripts/test/bar/package_bodies/TEST_PKG_TEST_ME.pkb @@ -0,0 +1,126 @@ +CREATE OR REPLACE PACKAGE BODY TESTS_OWNER.TEST_PKG_TEST_ME AS + + --------------------------------------------------------------------------- + PROCEDURE SETUP_GLOBAL IS + BEGIN + -- Put here the code which is valid for all tests and that should be + -- executed once. + NULL; + END SETUP_GLOBAL; + + --------------------------------------------------------------------------- + PROCEDURE TEARDOWN_GLOBAL IS + BEGIN + -- Put here the code that should be called only once after all the test + -- have executed + NULL; + END TEARDOWN_GLOBAL; + + --------------------------------------------------------------------------- + PROCEDURE SETUP_TEST IS + BEGIN + -- Nothing to clean up globally + NULL; + END SETUP_TEST; + + PROCEDURE TEARDOWN_TEST IS + BEGIN + -- Nothing to clean up globally + NULL; + END TEARDOWN_TEST; + + PROCEDURE TEST_FC_INPUT_1 IS + BEGIN + -- Ok this is a real test where I check that the function return 1 + -- when called with a '1' parameter + UT.EXPECT(PKG_TEST_ME.FC_TEST_ME('1')).TO_EQUAL(1); + END; + + PROCEDURE SETUP_TEST_FC_INPUT_1 IS + BEGIN + -- Nothing to be done really + NULL; + END; + + PROCEDURE TEARDOWN_TEST_FC_INPUT_1 IS + BEGIN + -- Nothing to be done really + NULL; + END; + + PROCEDURE TEST_FC_INPUT_0 IS + BEGIN + -- Ok this is a real test where I check that the function return 0 + -- when called with a '0' parameter + UT.EXPECT(PKG_TEST_ME.FC_TEST_ME('0')).TO_EQUAL(0); + END; + + PROCEDURE TEST_FC_INPUT_NULL IS + BEGIN + -- Ok I check that the function return NULL + -- when called with a NULL parameter + UT.EXPECT(PKG_TEST_ME.FC_TEST_ME(NULL)).TO_BE_NULL; + END TEST_FC_INPUT_NULL; + + PROCEDURE TEST_PR_TEST_ME_NULL IS + VNCOUNT1 PLS_INTEGER; + VNCOUNT2 PLS_INTEGER; + BEGIN + -- In this example I check that the procedure does + -- not insert anything when passing it a NULL parameter + SELECT COUNT(1) INTO VNCOUNT1 FROM TO_TEST_ME; + PKG_TEST_ME.PR_TEST_ME(NULL); + SELECT COUNT(1) INTO VNCOUNT2 FROM TO_TEST_ME; + UT.EXPECT(VNCOUNT1).TO_EQUAL(VNCOUNT2); + END; + + PROCEDURE TEST_PR_TEST_ME_NOT_NULL IS + VNCOUNT1 PLS_INTEGER; + VNCOUNT2 PLS_INTEGER; + VSNAME TO_TEST_ME.SNAME%TYPE; + BEGIN + -- In this test I will check that I do insert a value + -- when the parameter is not null. I futher check that + -- the procedure has inserted the value I specified. + SELECT COUNT(1) INTO VNCOUNT1 FROM TO_TEST_ME; + VSNAME := TO_CHAR(VNCOUNT1); + PKG_TEST_ME.PR_TEST_ME(VSNAME); + SELECT COUNT(1) INTO VNCOUNT2 FROM TO_TEST_ME; + + -- Check that I have inserted the value + UT.EXPECT(VNCOUNT1 + 1).TO_EQUAL(VNCOUNT2); + SELECT COUNT(1) INTO VNCOUNT2 FROM TO_TEST_ME T WHERE T.SNAME = VSNAME; + + -- Check that I inserted the one I said I would insert + UT.EXPECT(VNCOUNT2).TO_EQUAL(1); + DELETE FROM TO_TEST_ME T WHERE T.SNAME = VSNAME; + COMMIT; + END; + + PROCEDURE TEST_PR_TEST_ME_EXISTS IS + BEGIN + -- In case the value exists the procedure should fail with an exception. + BEGIN + PKG_TEST_ME.PR_TEST_ME('EXISTS'); + PKG_TEST_ME.PR_TEST_ME('EXISTS'); + EXCEPTION + WHEN OTHERS THEN + UT.FAIL('Unexpected exception raised'); + END; + END; + + PROCEDURE TEST_PR_TEST_ME_CURSOR IS + TYPE REF_CURSOR IS REF CURSOR; + VEXPECTED REF_CURSOR; + VACTUAL REF_CURSOR; + BEGIN + EXECUTE IMMEDIATE 'TRUNCATE TABLE CODE_OWNER.TO_TEST_ME'; + OPEN VEXPECTED FOR + SELECT T.SNAME FROM TO_TEST_ME T; + OPEN VACTUAL FOR + SELECT T.SNAME FROM TO_TEST_ME T; + UT.EXPECT(VEXPECTED).TO_(EQUAL(VACTUAL)); + END; + +END; +/ diff --git a/scripts/sql/owner_param/scripts/test/bar/packages/TEST_PKG_TEST_ME.pks b/scripts/sql/owner_param/scripts/test/bar/packages/TEST_PKG_TEST_ME.pks new file mode 100644 index 0000000..b0cdf54 --- /dev/null +++ b/scripts/sql/owner_param/scripts/test/bar/packages/TEST_PKG_TEST_ME.pks @@ -0,0 +1,86 @@ +CREATE OR REPLACE PACKAGE TESTS_OWNER.TEST_PKG_TEST_ME AS + -- %suite(TEST_PKG_TEST_ME) + -- %suitepath(plsql.examples) + -- + -- This package shows all the possibilities to unit test + -- your PL/SQL package. NOTE that it is not limited to + -- testing your package. You can do that on all your + -- procedure/functions... + -- + + /** + * This two parameters are used by the test framework in + * order to identify the test suite to run + */ + + /* + * This method is invoked once before any other method. + * It should contain all the setup code that is relevant + * for all your test. It might be inserting a register, + * creating a type, etc... + */ + -- %beforeall + PROCEDURE SETUP_GLOBAL; + + /* + * This method is invoked once after all other method. + * It can be used to clean up all the resources that + * you created in your script + */ + -- %afterall + PROCEDURE TEARDOWN_GLOBAL; + + /* + * This method is called once before each test. + */ + -- %beforeeach + PROCEDURE SETUP_TEST; + + /* + * This method is called once after each test. + */ + -- %aftereach + PROCEDURE TEARDOWN_TEST; + + /** + * This is a real test. The main test can declare a setup + * and teardown method in order to setup and cleanup things + * for that specific test. + */ + -- %test + -- %displayname(Checking if function ('1') returns 1) + -- %beforetest(SETUP_TEST_FC_INPUT_1) + -- %aftertest(TEARDOWN_TEST_FC_INPUT_1) + PROCEDURE TEST_FC_INPUT_1; + PROCEDURE SETUP_TEST_FC_INPUT_1; + PROCEDURE TEARDOWN_TEST_FC_INPUT_1; + + -- %test + -- %displayname(Checking if function ('0') returns 0) + PROCEDURE TEST_FC_INPUT_0; + + -- %test + -- %displayname(Checking if function (NULL) returns NULL) + PROCEDURE TEST_FC_INPUT_NULL; + + -- %test + -- %displayname(Checking if procedure (NULL) insert) + PROCEDURE TEST_PR_TEST_ME_NULL; + + -- %test + -- %displayname(Checking if procedure (NOT NULL) insert) + -- %rollback(manual) + PROCEDURE TEST_PR_TEST_ME_NOT_NULL; + + -- %test + -- %displayname(Checking if procedure (NOT NULL) insert while existing) + -- %rollback(manual) + PROCEDURE TEST_PR_TEST_ME_EXISTS; + + -- %test + -- %displayname(Demonstrating the use of cursor) + -- %rollback(manual) + PROCEDURE TEST_PR_TEST_ME_CURSOR; + +END; +/ diff --git a/scripts/sql/simple/scripts/sources/APP.PKG_TEST_ME.pkb b/scripts/sql/simple/scripts/sources/APP.PKG_TEST_ME.pkb new file mode 100644 index 0000000..331959d --- /dev/null +++ b/scripts/sql/simple/scripts/sources/APP.PKG_TEST_ME.pkb @@ -0,0 +1,27 @@ +CREATE OR REPLACE PACKAGE BODY PKG_TEST_ME IS + -- + -- This + -- + FUNCTION FC_TEST_ME(PPARAM1 IN VARCHAR2) RETURN NUMBER IS + BEGIN + IF PPARAM1 IS NULL THEN + RETURN NULL; + ELSIF PPARAM1 = '1' THEN + RETURN 1; + ELSE + RETURN 0; + END IF; + END FC_TEST_ME; + + PROCEDURE PR_TEST_ME(PSNAME IN VARCHAR2) IS + BEGIN + IF PSNAME IS NULL THEN + NULL; + ELSE + INSERT INTO TO_TEST_ME (SNAME) VALUES (PSNAME); + COMMIT; + END IF; + END PR_TEST_ME; + +END PKG_TEST_ME; +/ diff --git a/scripts/sql/simple/scripts/sources/APP.PKG_TEST_ME.pks b/scripts/sql/simple/scripts/sources/APP.PKG_TEST_ME.pks new file mode 100644 index 0000000..26b2589 --- /dev/null +++ b/scripts/sql/simple/scripts/sources/APP.PKG_TEST_ME.pks @@ -0,0 +1,8 @@ +-- +-- This package is used TO demonstrate the utPL/SQL possibilities +-- +CREATE OR REPLACE PACKAGE PKG_TEST_ME AS + FUNCTION FC_TEST_ME(PPARAM1 IN VARCHAR2) RETURN NUMBER; + PROCEDURE PR_TEST_ME(PSNAME IN VARCHAR2); +END PKG_TEST_ME; +/ \ No newline at end of file diff --git a/scripts/sql/simple/scripts/sources/TO_TEST_ME.sql b/scripts/sql/simple/scripts/sources/TO_TEST_ME.sql new file mode 100644 index 0000000..b2e90d9 --- /dev/null +++ b/scripts/sql/simple/scripts/sources/TO_TEST_ME.sql @@ -0,0 +1,8 @@ +-- +-- This is a table used to demonstrate the UNIT test framework. +-- +CREATE TABLE TO_TEST_ME +( + SNAME VARCHAR2(10) +) +/ diff --git a/scripts/sql/simple/scripts/tests/APP.TEST_PKG_TEST_ME.pkb b/scripts/sql/simple/scripts/tests/APP.TEST_PKG_TEST_ME.pkb new file mode 100644 index 0000000..115bc15 --- /dev/null +++ b/scripts/sql/simple/scripts/tests/APP.TEST_PKG_TEST_ME.pkb @@ -0,0 +1,126 @@ +CREATE OR REPLACE PACKAGE BODY TEST_PKG_TEST_ME AS + + --------------------------------------------------------------------------- + PROCEDURE SETUP_GLOBAL IS + BEGIN + -- Put here the code which is valid for all tests and that should be + -- executed once. + NULL; + END SETUP_GLOBAL; + + --------------------------------------------------------------------------- + PROCEDURE TEARDOWN_GLOBAL IS + BEGIN + -- Put here the code that should be called only once after all the test + -- have executed + NULL; + END TEARDOWN_GLOBAL; + + --------------------------------------------------------------------------- + PROCEDURE SETUP_TEST IS + BEGIN + -- Nothing to clean up globally + NULL; + END SETUP_TEST; + + PROCEDURE TEARDOWN_TEST IS + BEGIN + -- Nothing to clean up globally + NULL; + END TEARDOWN_TEST; + + PROCEDURE TEST_FC_INPUT_1 IS + BEGIN + -- Ok this is a real test where I check that the function return 1 + -- when called with a '1' parameter + UT.EXPECT(PKG_TEST_ME.FC_TEST_ME('1')).TO_EQUAL(1); + END; + + PROCEDURE SETUP_TEST_FC_INPUT_1 IS + BEGIN + -- Nothing to be done really + NULL; + END; + + PROCEDURE TEARDOWN_TEST_FC_INPUT_1 IS + BEGIN + -- Nothing to be done really + NULL; + END; + + PROCEDURE TEST_FC_INPUT_0 IS + BEGIN + -- Ok this is a real test where I check that the function return 0 + -- when called with a '0' parameter + UT.EXPECT(PKG_TEST_ME.FC_TEST_ME('0')).TO_EQUAL(0); + END; + + PROCEDURE TEST_FC_INPUT_NULL IS + BEGIN + -- Ok I check that the function return NULL + -- when called with a NULL parameter + UT.EXPECT(PKG_TEST_ME.FC_TEST_ME(NULL)).TO_BE_NULL; + END TEST_FC_INPUT_NULL; + + PROCEDURE TEST_PR_TEST_ME_NULL IS + VNCOUNT1 PLS_INTEGER; + VNCOUNT2 PLS_INTEGER; + BEGIN + -- In this example I check that the procedure does + -- not insert anything when passing it a NULL parameter + SELECT COUNT(1) INTO VNCOUNT1 FROM TO_TEST_ME; + PKG_TEST_ME.PR_TEST_ME(NULL); + SELECT COUNT(1) INTO VNCOUNT2 FROM TO_TEST_ME; + UT.EXPECT(VNCOUNT1).TO_EQUAL(VNCOUNT2); + END; + + PROCEDURE TEST_PR_TEST_ME_NOT_NULL IS + VNCOUNT1 PLS_INTEGER; + VNCOUNT2 PLS_INTEGER; + VSNAME TO_TEST_ME.SNAME%TYPE; + BEGIN + -- In this test I will check that I do insert a value + -- when the parameter is not null. I futher check that + -- the procedure has inserted the value I specified. + SELECT COUNT(1) INTO VNCOUNT1 FROM TO_TEST_ME; + VSNAME := TO_CHAR(VNCOUNT1); + PKG_TEST_ME.PR_TEST_ME(VSNAME); + SELECT COUNT(1) INTO VNCOUNT2 FROM TO_TEST_ME; + + -- Check that I have inserted the value + UT.EXPECT(VNCOUNT1 + 1).TO_EQUAL(VNCOUNT2); + SELECT COUNT(1) INTO VNCOUNT2 FROM TO_TEST_ME T WHERE T.SNAME = VSNAME; + + -- Check that I inserted the one I said I would insert + UT.EXPECT(VNCOUNT2).TO_EQUAL(1); + DELETE FROM TO_TEST_ME T WHERE T.SNAME = VSNAME; + COMMIT; + END; + + PROCEDURE TEST_PR_TEST_ME_EXISTS IS + BEGIN + -- In case the value exists the procedure should fail with an exception. + BEGIN + PKG_TEST_ME.PR_TEST_ME('EXISTS'); + PKG_TEST_ME.PR_TEST_ME('EXISTS'); + EXCEPTION + WHEN OTHERS THEN + UT.FAIL('Unexpected exception raised'); + END; + END; + + PROCEDURE TEST_PR_TEST_ME_CURSOR IS + TYPE REF_CURSOR IS REF CURSOR; + VEXPECTED REF_CURSOR; + VACTUAL REF_CURSOR; + BEGIN + EXECUTE IMMEDIATE 'TRUNCATE TABLE TO_TEST_ME'; + OPEN VEXPECTED FOR + SELECT T.SNAME FROM TO_TEST_ME T; + OPEN VACTUAL FOR + SELECT T.SNAME FROM TO_TEST_ME T; + UT.EXPECT(VEXPECTED).TO_(EQUAL(VACTUAL)); + END; + +END; +/ diff --git a/scripts/sql/simple/scripts/tests/APP.TEST_PKG_TEST_ME.pks b/scripts/sql/simple/scripts/tests/APP.TEST_PKG_TEST_ME.pks new file mode 100644 index 0000000..8a2b852 --- /dev/null +++ b/scripts/sql/simple/scripts/tests/APP.TEST_PKG_TEST_ME.pks @@ -0,0 +1,88 @@ +CREATE OR REPLACE PACKAGE TEST_PKG_TEST_ME AS + -- %suite(TEST_PKG_TEST_ME) + -- %suitepath(plsql.examples) + -- + -- This package shows all the possibilities to unit test + -- your PL/SQL package. NOTE that it is not limited to + -- testing your package. You can do that on all your + -- procedure/functions... + -- + + /** + * This two parameters are used by the test framework in + * order to identify the test suite to run + */ + + /* + * This method is invoked once before any other method. + * It should contain all the setup code that is relevant + * for all your test. It might be inserting a register, + * creating a type, etc... + */ + -- %beforeall + PROCEDURE SETUP_GLOBAL; + + /* + * This method is invoked once after all other method. + * It can be used to clean up all the resources that + * you created in your script + */ + -- %afterall + PROCEDURE TEARDOWN_GLOBAL; + + /* + * This method is called once before each test. + */ + -- %beforeeach + PROCEDURE SETUP_TEST; + + /* + * This method is called once after each test. + */ + -- %aftereach + PROCEDURE TEARDOWN_TEST; + + /** + * This is a real test. The main test can declare a setup + * and teardown method in order to setup and cleanup things + * for that specific test. + */ + -- %test + -- %displayname(Checking if function ('1') returns 1) + -- %beforetest(SETUP_TEST_FC_INPUT_1) + -- %aftertest(TEARDOWN_TEST_FC_INPUT_1) + PROCEDURE TEST_FC_INPUT_1; + PROCEDURE SETUP_TEST_FC_INPUT_1; + PROCEDURE TEARDOWN_TEST_FC_INPUT_1; + + -- %test + -- %displayname(Checking if function ('0') returns 0) + PROCEDURE TEST_FC_INPUT_0; + + -- %test + -- %displayname(Checking if function (NULL) returns NULL) + PROCEDURE TEST_FC_INPUT_NULL; + + -- %test + -- %displayname(Checking if procedure (NULL) insert) + PROCEDURE TEST_PR_TEST_ME_NULL; + + -- %test + -- %displayname(Checking if procedure (NOT NULL) insert) + -- %rollback(manual) + PROCEDURE TEST_PR_TEST_ME_NOT_NULL; + + -- %test + -- %displayname(Checking if procedure (NOT NULL) insert while existing) + -- %rollback(manual) + -- %tags(exists) + PROCEDURE TEST_PR_TEST_ME_EXISTS; + + -- %test + -- %displayname(Demonstrating the use of cursor) + -- %rollback(manual) + -- %tags(cursor) + PROCEDURE TEST_PR_TEST_ME_CURSOR; + +END; +/ diff --git a/src/main/java/org/utplsql/api/CustomTypes.java b/src/main/java/org/utplsql/api/CustomTypes.java index 45dda22..1c04c23 100644 --- a/src/main/java/org/utplsql/api/CustomTypes.java +++ b/src/main/java/org/utplsql/api/CustomTypes.java @@ -10,13 +10,6 @@ public final class CustomTypes { public static final String UT_VARCHAR2_LIST = "UT_VARCHAR2_LIST"; public static final String UT_REPORTERS = "UT_REPORTERS"; - public static final String UT_DOCUMENTATION_REPORTER = "UT_DOCUMENTATION_REPORTER"; - public static final String UT_COVERAGE_HTML_REPORTER = "UT_COVERAGE_HTML_REPORTER"; - public static final String UT_TEAMCITY_REPORTER = "UT_TEAMCITY_REPORTER"; - public static final String UT_XUNIT_REPORTER = "UT_XUNIT_REPORTER"; - public static final String UT_COVERALLS_REPORTER = "UT_COVERALLS_REPORTER"; - public static final String UT_COVERAGE_SONAR_REPORTER = "UT_COVERAGE_SONAR_REPORTER"; - public static final String UT_SONAR_TEST_REPORTER = "UT_SONAR_TEST_REPORTER"; public static final String UT_FILE_MAPPING = "UT_FILE_MAPPING"; public static final String UT_FILE_MAPPINGS = "UT_FILE_MAPPINGS"; @@ -24,6 +17,7 @@ public final class CustomTypes { public static final String UT_KEY_VALUE_PAIR = "UT_KEY_VALUE_PAIR"; public static final String UT_KEY_VALUE_PAIRS = "UT_KEY_VALUE_PAIRS"; - private CustomTypes() {} + private CustomTypes() { + } } diff --git a/src/main/java/org/utplsql/api/DBHelper.java b/src/main/java/org/utplsql/api/DBHelper.java index 412e970..09c109c 100644 --- a/src/main/java/org/utplsql/api/DBHelper.java +++ b/src/main/java/org/utplsql/api/DBHelper.java @@ -1,85 +1,86 @@ package org.utplsql.api; import oracle.jdbc.OracleTypes; -import org.utplsql.api.exception.DatabaseNotCompatibleException; -import org.utplsql.api.exception.UtPLSQLNotInstalledException; +import org.utplsql.api.db.DatabaseInformation; +import org.utplsql.api.db.DefaultDatabaseInformation; -import java.sql.*; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Types; /** * Database utility functions. */ public final class DBHelper { - private DBHelper() {} + private DBHelper() { + } /** * Return a new sys_guid from database. + * * @param conn the connection * @return the new id string * @throws SQLException any database error */ public static String newSysGuid(Connection conn) throws SQLException { - CallableStatement callableStatement = null; - try { - callableStatement = conn.prepareCall("BEGIN ? := sys_guid(); END;"); + try (CallableStatement callableStatement = conn.prepareCall("BEGIN ? := sys_guid(); END;")) { callableStatement.registerOutParameter(1, OracleTypes.RAW); callableStatement.executeUpdate(); return callableStatement.getString(1); - } finally { - if (callableStatement != null) - callableStatement.close(); } } /** * Return the current schema name. + * Deprecated. Use DatabaseInformation-Interface instead. + * * @param conn the connection * @return the schema name * @throws SQLException any database error */ + @Deprecated public static String getCurrentSchema(Connection conn) throws SQLException { - CallableStatement callableStatement = null; - try { - callableStatement = conn.prepareCall("BEGIN ? := sys_context('userenv', 'current_schema'); END;"); + try (CallableStatement callableStatement = conn.prepareCall("BEGIN ? := sys_context('userenv', 'current_schema'); END;")) { callableStatement.registerOutParameter(1, Types.VARCHAR); callableStatement.executeUpdate(); return callableStatement.getString(1); - } finally { - if (callableStatement != null) - callableStatement.close(); } } - /** Returns the Frameworks version string of the given connection + /** + * Returns the Frameworks version string of the given connection + * Deprecated. Use DatabaseInformation-Interface instead. * * @param conn Active db connection - * @return - * @throws SQLException + * @return Version-string of the utPLSQL framework + * @throws SQLException any database error */ - public static Version getDatabaseFrameworkVersion( Connection conn ) - throws SQLException { - Version result = new Version(""); - try (PreparedStatement stmt = conn.prepareStatement("select ut_runner.version() from dual")) - { - ResultSet rs = stmt.executeQuery(); - - if ( rs.next() ) - result = new Version(rs.getString(1)); + @Deprecated + public static Version getDatabaseFrameworkVersion(Connection conn) throws SQLException { + DatabaseInformation databaseInformation = new DefaultDatabaseInformation(); + return databaseInformation.getUtPlsqlFrameworkVersion(conn); - rs.close(); - } catch ( SQLException e ) { - if ( e.getErrorCode() == UtPLSQLNotInstalledException.ERROR_CODE ) - throw new UtPLSQLNotInstalledException(e); - else - throw e; - } + } - return result; + /** + * Returns the Oracle database Version from a given connection object + * Deprecated. Use DatabaseInformation-Interface instead. + * + * @param conn Connection-Object + * @return Returns version-string of the Oracle Database product component + * @throws SQLException any database error + */ + @Deprecated + public static String getOracleDatabaseVersion(Connection conn) throws SQLException { + DatabaseInformation databaseInformation = new DefaultDatabaseInformation(); + return databaseInformation.getOracleVersion(conn); } /** * Enable the dbms_output buffer with unlimited size. + * * @param conn the connection */ public static void enableDBMSOutput(Connection conn) { @@ -92,6 +93,7 @@ public static void enableDBMSOutput(Connection conn) { /** * Disable the dbms_output buffer. + * * @param conn the connection */ public static void disableDBMSOutput(Connection conn) { diff --git a/src/main/java/org/utplsql/api/EnvironmentVariableUtil.java b/src/main/java/org/utplsql/api/EnvironmentVariableUtil.java new file mode 100644 index 0000000..90ca49f --- /dev/null +++ b/src/main/java/org/utplsql/api/EnvironmentVariableUtil.java @@ -0,0 +1,53 @@ +package org.utplsql.api; + +import javax.annotation.Nullable; + +/** + * This class provides an easy way to get environmental variables. + * This is mainly to improve testability but also to standardize the way how utPLSQL API and CLI read from + * environment. + *

+ * Variables are obtained from the following scopes in that order (chain breaks as soon as a value is obtained): + *

    + *
  • Properties (System.getProperty())
  • + *
  • Environment (System.getEnv())
  • + *
  • Default value
  • + *
+ *

+ * An empty string is treated the same as null. + * + * @author pesse + */ +public class EnvironmentVariableUtil { + + private EnvironmentVariableUtil() { + } + + /** + * Returns the value for a given key from environment (see class description) + * + * @param key Key of environment or property value + * @return Environment value or null + */ + public static String getEnvValue(String key) { + return getEnvValue(key, null); + } + + /** + * Returns the value for a given key from environment or a default value (see class description) + * + * @param key Key of environment or property value + * @param defaultValue Default value if nothing found + * @return Environment value or defaultValue + */ + public static String getEnvValue(String key, @Nullable String defaultValue) { + + String val = System.getProperty(key); + if (val == null || val.isEmpty()) val = System.getenv(key); + if (val == null || val.isEmpty()) val = defaultValue; + + return val; + } + + +} diff --git a/src/main/java/org/utplsql/api/FileMapper.java b/src/main/java/org/utplsql/api/FileMapper.java deleted file mode 100644 index cdea084..0000000 --- a/src/main/java/org/utplsql/api/FileMapper.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.utplsql.api; - - -import oracle.jdbc.OracleConnection; -import oracle.jdbc.OracleTypes; - -import java.sql.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public final class FileMapper { - - private FileMapper() {} - - /** - * Call the database api to build the custom file mappings. - */ - public static Array buildFileMappingArray( - Connection conn, FileMapperOptions mapperOptions) throws SQLException { - OracleConnection oraConn = conn.unwrap(OracleConnection.class); - - Map typeMap = conn.getTypeMap(); - typeMap.put(CustomTypes.UT_FILE_MAPPING, FileMapping.class); - typeMap.put(CustomTypes.UT_KEY_VALUE_PAIR, KeyValuePair.class); - conn.setTypeMap(typeMap); - - CallableStatement callableStatement = conn.prepareCall( - "BEGIN " + - "? := ut_file_mapper.build_file_mappings(" + - "a_object_owner => ?, " + - "a_file_paths => ?, " + - "a_file_to_object_type_mapping => ?, " + - "a_regex_pattern => ?, " + - "a_object_owner_subexpression => ?, " + - "a_object_name_subexpression => ?, " + - "a_object_type_subexpression => ?); " + - "END;"); - - int paramIdx = 0; - callableStatement.registerOutParameter(++paramIdx, OracleTypes.ARRAY, CustomTypes.UT_FILE_MAPPINGS); - - if (mapperOptions.getObjectOwner() == null) { - callableStatement.setNull(++paramIdx, Types.VARCHAR); - } else { - callableStatement.setString(++paramIdx, mapperOptions.getObjectOwner()); - } - - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_VARCHAR2_LIST, mapperOptions.getFilePaths().toArray())); - - if (mapperOptions.getTypeMappings() == null) { - callableStatement.setNull(++paramIdx, Types.ARRAY, CustomTypes.UT_KEY_VALUE_PAIRS); - } else { - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_KEY_VALUE_PAIRS, mapperOptions.getTypeMappings().toArray())); - } - - if (mapperOptions.getRegexPattern() == null) { - callableStatement.setNull(++paramIdx, Types.VARCHAR); - } else { - callableStatement.setString(++paramIdx, mapperOptions.getRegexPattern()); - } - - if (mapperOptions.getOwnerSubExpression() == null) { - callableStatement.setNull(++paramIdx, Types.INTEGER); - } else { - callableStatement.setInt(++paramIdx, mapperOptions.getOwnerSubExpression()); - } - - if (mapperOptions.getNameSubExpression() == null) { - callableStatement.setNull(++paramIdx, Types.INTEGER); - } else { - callableStatement.setInt(++paramIdx, mapperOptions.getNameSubExpression()); - } - - if (mapperOptions.getTypeSubExpression() == null) { - callableStatement.setNull(++paramIdx, Types.INTEGER); - } else { - callableStatement.setInt(++paramIdx, mapperOptions.getTypeSubExpression()); - } - - callableStatement.execute(); - return callableStatement.getArray(1); - } - - public static List buildFileMappingList( - Connection conn, FileMapperOptions mapperOptions) throws SQLException { - java.sql.Array fileMappings = buildFileMappingArray(conn, mapperOptions); - - List mappingList = new ArrayList<>(); - for (Object obj : (Object[]) fileMappings.getArray()) { - mappingList.add((FileMapping) obj); - } - - return mappingList; - } - -} diff --git a/src/main/java/org/utplsql/api/FileMapping.java b/src/main/java/org/utplsql/api/FileMapping.java index eb383dd..be86e2a 100644 --- a/src/main/java/org/utplsql/api/FileMapping.java +++ b/src/main/java/org/utplsql/api/FileMapping.java @@ -15,13 +15,21 @@ public class FileMapping implements SQLData { private String objectName; private String objectType; - public FileMapping() {} + public FileMapping() { + } + + public FileMapping(String fileName, String objectOwner, String objectName, String objectType) { + this.fileName = fileName; + this.objectOwner = objectOwner; + this.objectName = objectName; + this.objectType = objectType; + } public String getFileName() { return fileName; } - public void setFileName(String fileName) { + private void setFileName(String fileName) { this.fileName = fileName; } @@ -29,7 +37,7 @@ public String getObjectOwner() { return objectOwner; } - public void setObjectOwner(String objectOwner) { + private void setObjectOwner(String objectOwner) { this.objectOwner = objectOwner; } @@ -37,7 +45,7 @@ public String getObjectName() { return objectName; } - public void setObjectName(String objectName) { + private void setObjectName(String objectName) { this.objectName = objectName; } @@ -45,12 +53,12 @@ public String getObjectType() { return objectType; } - public void setObjectType(String objectType) { + private void setObjectType(String objectType) { this.objectType = objectType; } @Override - public String getSQLTypeName() throws SQLException { + public String getSQLTypeName() { return CustomTypes.UT_FILE_MAPPING; } diff --git a/src/main/java/org/utplsql/api/JavaApiVersionInfo.java b/src/main/java/org/utplsql/api/JavaApiVersionInfo.java new file mode 100644 index 0000000..48e529b --- /dev/null +++ b/src/main/java/org/utplsql/api/JavaApiVersionInfo.java @@ -0,0 +1,43 @@ +package org.utplsql.api; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * This class is getting updated automatically by the build process. + * Please do not update its constants manually because they will be overwritten. + * + * @author pesse + */ +public class JavaApiVersionInfo { + + private static final String MAVEN_PROJECT_NAME = "utPLSQL-java-api"; + private static String MAVEN_PROJECT_VERSION = "unknown"; + + static { + try { + try (InputStream in = JavaApiVersionInfo.class.getClassLoader().getResourceAsStream("utplsql-api.version")) { + assert in != null; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { + MAVEN_PROJECT_VERSION = reader.readLine(); + } + } + } catch (IOException e) { + System.out.println("WARNING: Could not get Version information!"); + } + } + + private JavaApiVersionInfo() { + } + + public static String getVersion() { + return MAVEN_PROJECT_VERSION; + } + + public static String getInfo() { + return MAVEN_PROJECT_NAME + " " + getVersion(); + } + +} diff --git a/src/main/java/org/utplsql/api/KeyValuePair.java b/src/main/java/org/utplsql/api/KeyValuePair.java index 5826f45..c71a3ae 100644 --- a/src/main/java/org/utplsql/api/KeyValuePair.java +++ b/src/main/java/org/utplsql/api/KeyValuePair.java @@ -22,38 +22,30 @@ public String getKey() { return key; } - public void setKey(String key) { - this.key = key; - } - public String getValue() { return value; } - public void setValue(String value) { - this.value = value; - } - @Override - public String getSQLTypeName() throws SQLException { + public String getSQLTypeName() { return CustomTypes.UT_KEY_VALUE_PAIR; } @Override public void readSQL(SQLInput stream, String typeName) throws SQLException { - setKey(stream.readString()); - setValue(stream.readString()); + key = stream.readString(); + value = stream.readString(); } @Override public void writeSQL(SQLOutput stream) throws SQLException { - stream.writeString(getKey()); - stream.writeString(getValue()); + stream.writeString(key); + stream.writeString(value); } @Override public String toString() { - return String.format("%s => %s", getKey(), getValue()); + return String.format("%s => %s", key, value); } } diff --git a/src/main/java/org/utplsql/api/OutputBuffer.java b/src/main/java/org/utplsql/api/OutputBuffer.java deleted file mode 100644 index 6913bf6..0000000 --- a/src/main/java/org/utplsql/api/OutputBuffer.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.utplsql.api; - -import org.utplsql.api.reporter.Reporter; -import oracle.jdbc.OracleTypes; - -import java.io.PrintStream; -import java.sql.*; -import java.util.ArrayList; -import java.util.List; - -/** - * Fetches the lines produced by a reporter. - */ -public class OutputBuffer { - - private Reporter reporter; - - /** - * Creates a new OutputBuffer. - * @param reporter the reporter to be used - */ - public OutputBuffer(Reporter reporter) { - this.reporter = reporter; - } - - /** - * Returns the reporter used by this buffer. - * @return the reporter instance - */ - public Reporter getReporter() { - return reporter; - } - - /** - * Print the lines as soon as they are produced and write to a PrintStream. - * @param conn DB connection - * @param ps the PrintStream to be used, e.g: System.out - * @throws SQLException any sql errors - */ - public void printAvailable(Connection conn, PrintStream ps) throws SQLException { - List printStreams = new ArrayList<>(1); - printStreams.add(ps); - printAvailable(conn, printStreams); - } - - /** - * Print the lines as soon as they are produced and write to a list of PrintStreams. - * @param conn DB connection - * @param printStreams the PrintStream list to be used, e.g: System.out, new PrintStream(new FileOutputStream) - * @throws SQLException any sql errors - */ - public void printAvailable(Connection conn, List printStreams) throws SQLException { - fetchAvailable(conn, s -> { - for (PrintStream ps : printStreams) - ps.println(s); - }); - } - - /** - * Print the lines as soon as they are produced and call the callback passing the new line. - * @param conn DB connection - * @param cb the callback to be called - * @throws SQLException any sql errors - */ - public void fetchAvailable(Connection conn, Callback cb) throws SQLException { - PreparedStatement preparedStatement = null; - ResultSet resultSet = null; - try { - preparedStatement = conn.prepareStatement("SELECT * FROM table(ut_output_buffer.get_lines(?))"); - preparedStatement.setString(1, getReporter().getReporterId()); - resultSet = preparedStatement.executeQuery(); - - while (resultSet.next()) - cb.onLineFetched(resultSet.getString(1)); - } finally { - if (resultSet != null) - resultSet.close(); - if (preparedStatement != null) - preparedStatement.close(); - } - } - - /** - * Get all lines from output buffer and return it as a list of strings. - * @param conn DB connection - * @return the lines - * @throws SQLException any sql errors - */ - public List fetchAll(Connection conn) throws SQLException { - CallableStatement callableStatement = null; - ResultSet resultSet = null; - try { - callableStatement = conn.prepareCall("BEGIN ? := ut_output_buffer.get_lines_cursor(?); END;"); - callableStatement.registerOutParameter(1, OracleTypes.CURSOR); - callableStatement.setString(2, getReporter().getReporterId()); - callableStatement.execute(); - - resultSet = (ResultSet) callableStatement.getObject(1); - - List outputLines = new ArrayList<>(); - while (resultSet.next()) { - outputLines.add(resultSet.getString("text")); - } - return outputLines; - } finally { - if (resultSet != null) - resultSet.close(); - if (callableStatement != null) - callableStatement.close(); - } - } - - /** - * Callback to be called when a new line is available from the output buffer. - */ - public interface Callback { - void onLineFetched(String s); - } - -} diff --git a/src/main/java/org/utplsql/api/ResourceUtil.java b/src/main/java/org/utplsql/api/ResourceUtil.java new file mode 100644 index 0000000..d4ac0b3 --- /dev/null +++ b/src/main/java/org/utplsql/api/ResourceUtil.java @@ -0,0 +1,65 @@ +package org.utplsql.api; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; + +/** + * Helper class for dealing with Resources + * + * @author pesse + */ +public class ResourceUtil { + + private ResourceUtil() { + } + + /** + * Copy directory from a jar file to the destination folder + * + * @param resourceAsPath The resource to get children from + * @param targetDirectory If set to true it will only return files, not directories + */ + public static void copyResources(Path resourceAsPath, Path targetDirectory) { + try { + String resourceName = "/" + resourceAsPath; + Files.createDirectories(targetDirectory); + URI uri = ResourceUtil.class.getResource(resourceName).toURI(); + Path myPath; + if (uri.getScheme().equalsIgnoreCase("jar")) { + try (FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap())) { + myPath = fileSystem.getPath(resourceName); + copyRecursive(myPath, targetDirectory); + } + } else { + myPath = Paths.get(uri); + copyRecursive(myPath, targetDirectory); + } + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private static void copyRecursive(Path from, Path targetDirectory) throws IOException { + Files.walkFileTree(from, new SimpleFileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + super.preVisitDirectory(dir, attrs); + Path currentTarget = targetDirectory.resolve(from.relativize(dir).toString()); + Files.createDirectories(currentTarget); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + super.visitFile(file, attrs); + Files.copy(file, targetDirectory.resolve(from.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + }); + } +} diff --git a/src/main/java/org/utplsql/api/TestRunner.java b/src/main/java/org/utplsql/api/TestRunner.java index bbbee16..3a3f6e1 100644 --- a/src/main/java/org/utplsql/api/TestRunner.java +++ b/src/main/java/org/utplsql/api/TestRunner.java @@ -1,18 +1,24 @@ package org.utplsql.api; -import oracle.jdbc.OracleConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.utplsql.api.compatibility.CompatibilityProxy; -import org.utplsql.api.exception.DatabaseNotCompatibleException; +import org.utplsql.api.db.DatabaseInformation; +import org.utplsql.api.db.DefaultDatabaseInformation; +import org.utplsql.api.exception.OracleCreateStatmenetStuckException; import org.utplsql.api.exception.SomeTestsFailedException; import org.utplsql.api.exception.UtPLSQLNotInstalledException; import org.utplsql.api.reporter.DocumentationReporter; import org.utplsql.api.reporter.Reporter; +import org.utplsql.api.reporter.ReporterFactory; import org.utplsql.api.testRunner.TestRunnerStatement; -import java.sql.CallableStatement; import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.*; /** * Created by Vinicius Avellar on 12/04/2017. @@ -22,7 +28,12 @@ */ public class TestRunner { - private TestRunnerOptions options = new TestRunnerOptions(); + private static final Logger logger = LoggerFactory.getLogger(TestRunner.class); + + private final TestRunnerOptions options = new TestRunnerOptions(); + private final List reporterNames = new ArrayList<>(); + private CompatibilityProxy compatibilityProxy; + private ReporterFactory reporterFactory; public TestRunner addPath(String path) { options.pathList.add(path); @@ -30,7 +41,7 @@ public TestRunner addPath(String path) { } public TestRunner addPathList(List paths) { - if (options.pathList != null) options.pathList.addAll(paths); + options.pathList.addAll(paths); return this; } @@ -39,13 +50,22 @@ public TestRunner addReporter(Reporter reporter) { return this; } + public TestRunner addReporter(String reporterName) { + if (reporterFactory != null) { + options.reporterList.add(reporterFactory.createReporter(reporterName)); + } else { + reporterNames.add(reporterName); + } + return this; + } + public TestRunner colorConsole(boolean colorConsole) { options.colorConsole = colorConsole; return this; } public TestRunner addReporterList(List reporterList) { - if (options.reporterList != null) options.reporterList.addAll(reporterList); + options.reporterList.addAll(reporterList); return this; } @@ -54,6 +74,11 @@ public TestRunner addCoverageScheme(String coverageScheme) { return this; } + public TestRunner addCoverageSchemes(Collection schemaNames) { + this.options.coverageSchemes.addAll(schemaNames); + return this; + } + public TestRunner includeObject(String obj) { options.includeObjects.add(obj); return this; @@ -64,6 +89,36 @@ public TestRunner excludeObject(String obj) { return this; } + public TestRunner includeObjects(List obj) { + options.includeObjects.addAll(obj); + return this; + } + + public TestRunner excludeObjects(List obj) { + options.excludeObjects.addAll(obj); + return this; + } + + public TestRunner includeSchemaExpr(String expr) { + options.includeSchemaExpr = expr; + return this; + } + + public TestRunner excludeSchemaExpr(String expr) { + options.excludeSchemaExpr = expr; + return this; + } + + public TestRunner includeObjectExpr(String expr) { + options.includeObjectExpr = expr; + return this; + } + + public TestRunner excludeObjectExpr(String expr) { + options.excludeObjectExpr = expr; + return this; + } + public TestRunner sourceMappingOptions(FileMapperOptions mapperOptions) { options.sourceMappingOptions = mapperOptions; return this; @@ -79,75 +134,177 @@ public TestRunner failOnErrors(boolean failOnErrors) { return this; } - public TestRunner skipCompatibilityCheck( boolean skipCompatibilityCheck ) - { + public TestRunner skipCompatibilityCheck(boolean skipCompatibilityCheck) { options.skipCompatibilityCheck = skipCompatibilityCheck; return this; } - public void run(Connection conn) throws SomeTestsFailedException, SQLException, DatabaseNotCompatibleException, UtPLSQLNotInstalledException { + public TestRunner setReporterFactory(ReporterFactory reporterFactory) { + this.reporterFactory = reporterFactory; + return this; + } + + public TestRunner randomTestOrder(boolean randomTestOrder) { + this.options.randomTestOrder = randomTestOrder; + return this; + } + + public TestRunner randomTestOrderSeed(Integer seed) { + this.options.randomTestOrderSeed = seed; + if (seed != null) this.options.randomTestOrder = true; + return this; + } + + public TestRunner addTag(String tag) { + this.options.tags.add(tag); + return this; + } + + public TestRunner addTags(Collection tags) { + this.options.tags.addAll(tags); + return this; + } + + public TestRunner oraStuckTimeout(Integer oraStuckTimeout) { + this.options.oraStuckTimeout = oraStuckTimeout; + return this; + } + + public TestRunnerOptions getOptions() { + return options; + } + + private void delayedAddReporters() { + if (reporterFactory != null) { + reporterNames.forEach(this::addReporter); + } else { + throw new IllegalStateException("ReporterFactory must be set to add delayed Reporters!"); + } + } + + private void handleException(Throwable e) throws SQLException { + // Just pass exceptions already categorized + if (e instanceof UtPLSQLNotInstalledException) throw (UtPLSQLNotInstalledException) e; + else if (e instanceof SomeTestsFailedException) throw (SomeTestsFailedException) e; + else if (e instanceof OracleCreateStatmenetStuckException) throw (OracleCreateStatmenetStuckException) e; + // Categorize exceptions + else if (e instanceof SQLException) { + SQLException sqlException = (SQLException) e; + if (sqlException.getErrorCode() == SomeTestsFailedException.ERROR_CODE) { + throw new SomeTestsFailedException(sqlException.getMessage(), e); + } else if (((SQLException) e).getErrorCode() == UtPLSQLNotInstalledException.ERROR_CODE) { + throw new UtPLSQLNotInstalledException(sqlException); + } else { + throw sqlException; + } + } else { + throw new SQLException("Unknown exception, wrapping: " + e.getMessage(), e); + } + } + + public void run(Connection conn) throws SQLException { + + logger.info("TestRunner initialized"); + + DatabaseInformation databaseInformation = new DefaultDatabaseInformation(); + + if (options.skipCompatibilityCheck) { + compatibilityProxy = new CompatibilityProxy(conn, Version.LATEST, databaseInformation); + } else { + compatibilityProxy = new CompatibilityProxy(conn, databaseInformation); + } + logger.info("Running on utPLSQL {}", compatibilityProxy.getVersionDescription()); + + if (reporterFactory == null) { + reporterFactory = ReporterFactory.createDefault(compatibilityProxy); + } - CompatibilityProxy compatibilityProxy = new CompatibilityProxy(conn, options.skipCompatibilityCheck); + delayedAddReporters(); // First of all check version compatibility compatibilityProxy.failOnNotCompatible(); - for (Reporter r : options.reporterList) + logger.info("Initializing reporters"); + for (Reporter r : options.reporterList) { validateReporter(conn, r); + } if (options.pathList.isEmpty()) { - options.pathList.add(DBHelper.getCurrentSchema(conn)); + options.pathList.add(databaseInformation.getCurrentSchema(conn)); } if (options.reporterList.isEmpty()) { + logger.info("No reporter given so choosing ut_documentation_reporter"); options.reporterList.add(new DocumentationReporter().init(conn)); } TestRunnerStatement testRunnerStatement = null; - try { - DBHelper.enableDBMSOutput(conn); - - testRunnerStatement = compatibilityProxy.getTestRunnerStatement(options, conn); - + testRunnerStatement = (options.oraStuckTimeout > 0) ? initStatementWithTimeout(conn, options.oraStuckTimeout) : initStatement(conn); + logger.info("Running tests"); testRunnerStatement.execute(); + logger.info("Running tests finished."); + testRunnerStatement.close(); + } catch (OracleCreateStatmenetStuckException e) { + // Don't close statement in this case for it will be stuck, too + throw e; } catch (SQLException e) { - if (e.getErrorCode() == SomeTestsFailedException.ERROR_CODE) { - throw new SomeTestsFailedException(e.getMessage(), e); - } - else if (e.getErrorCode() == UtPLSQLNotInstalledException.ERROR_CODE) { - throw new UtPLSQLNotInstalledException(e); - } - else { - // If the execution failed by unexpected reasons finishes all reporters, - // this way the users don't need to care about reporters' sessions hanging. - OracleConnection oraConn = conn.unwrap(OracleConnection.class); + if (testRunnerStatement != null) testRunnerStatement.close(); + handleException(e); + } + } - try (CallableStatement closeBufferStmt = conn.prepareCall("BEGIN ut_output_buffer.close(?); END;")) { - closeBufferStmt.setArray(1, oraConn.createOracleArray(CustomTypes.UT_REPORTERS, options.reporterList.toArray())); - closeBufferStmt.execute(); - } catch (SQLException ignored) {} + private TestRunnerStatement initStatement(Connection conn) throws SQLException { + return compatibilityProxy.getTestRunnerStatement(options, conn); + } - throw e; - } - } finally { - if (testRunnerStatement != null) { - testRunnerStatement.close(); - } + private TestRunnerStatement initStatementWithTimeout(Connection conn, int timeout) throws SQLException { + TestRunnerStatement testRunnerStatement; + ExecutorService executor = Executors.newSingleThreadExecutor(); + Callable callable = () -> compatibilityProxy.getTestRunnerStatement(options, conn); + Future future = executor.submit(callable); - DBHelper.disableDBMSOutput(conn); + // We want to leave the statement open in case of stuck scenario + testRunnerStatement = null; + try { + testRunnerStatement = future.get(timeout, TimeUnit.SECONDS); + } catch (TimeoutException e) { + logger.error("Detected Oracle driver stuck during Statement initialization"); + executor.shutdownNow(); + throw new OracleCreateStatmenetStuckException(e); + } catch (InterruptedException e) { + handleException(e); + } catch (ExecutionException e) { + handleException(e.getCause()); } + + return testRunnerStatement; } /** * Check if the reporter was initialized, if not call reporter.init. - * @param conn the database connection + * + * @param conn the database connection * @param reporter the reporter * @throws SQLException any sql exception */ private void validateReporter(Connection conn, Reporter reporter) throws SQLException { - if (reporter.getReporterId() == null || reporter.getReporterId().isEmpty()) - reporter.init(conn); + if (!reporter.isInit() || reporter.getId() == null || reporter.getId().isEmpty()) { + reporter.init(conn, compatibilityProxy, reporterFactory); + } + } + + /** + * Returns the databaseVersion the TestRunner was run against + * + * @return Version of the database the TestRunner was run against + */ + public Version getUsedDatabaseVersion() { + if (compatibilityProxy != null) { + return compatibilityProxy.getUtPlsqlVersion(); + } else { + return null; + } } } diff --git a/src/main/java/org/utplsql/api/TestRunnerOptions.java b/src/main/java/org/utplsql/api/TestRunnerOptions.java index 65ea978..615e308 100644 --- a/src/main/java/org/utplsql/api/TestRunnerOptions.java +++ b/src/main/java/org/utplsql/api/TestRunnerOptions.java @@ -2,24 +2,41 @@ import org.utplsql.api.reporter.Reporter; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; -/** Holds the various possible options of TestRunner +/** + * Holds the various possible options of TestRunner * * @author pesse */ public class TestRunnerOptions { - public List pathList = new ArrayList<>(); - public List reporterList = new ArrayList<>(); + public final List pathList = new ArrayList<>(); + public final List reporterList = new ArrayList<>(); + public final List coverageSchemes = new ArrayList<>(); + public final List sourceFiles = new ArrayList<>(); + public final List testFiles = new ArrayList<>(); + public final List includeObjects = new ArrayList<>(); + public final List excludeObjects = new ArrayList<>(); + public String includeSchemaExpr; + public String excludeSchemaExpr; + public String includeObjectExpr; + public String excludeObjectExpr; public boolean colorConsole = false; - public List coverageSchemes = new ArrayList<>(); - public List sourceFiles = new ArrayList<>(); - public List testFiles = new ArrayList<>(); - public List includeObjects = new ArrayList<>(); - public List excludeObjects = new ArrayList<>(); public FileMapperOptions sourceMappingOptions; public FileMapperOptions testMappingOptions; public boolean failOnErrors = false; public boolean skipCompatibilityCheck = false; + public String clientCharacterSet = Charset.defaultCharset().toString(); + public boolean randomTestOrder = false; + public Integer randomTestOrderSeed; + public final Set tags = new LinkedHashSet<>(); + public Integer oraStuckTimeout = 0; + + public String getTagsAsString() { + return String.join(",", tags); + } } diff --git a/src/main/java/org/utplsql/api/Version.java b/src/main/java/org/utplsql/api/Version.java index afaeee4..5aaabfa 100644 --- a/src/main/java/org/utplsql/api/Version.java +++ b/src/main/java/org/utplsql/api/Version.java @@ -2,52 +2,123 @@ import org.utplsql.api.exception.InvalidVersionException; +import javax.annotation.Nullable; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; -/** Simple class to parse utPLSQL Version-information and provide the separate version-numbers +import static java.util.stream.Collectors.toMap; + +/** + * Simple class to parse utPLSQL Version-information and provide the separate version-numbers * * @author pesse */ public class Version implements Comparable { - private String origString; - private Integer major; - private Integer minor; - private Integer bugfix; - private Integer build; - private boolean valid = false; - public Version( String versionString ) { - assert versionString != null; - this.origString = versionString; - parseVersionString(); + public final static Version V3_0_0 = new Version("3.0.0", 3, 0, 0, null, true); + public final static Version V3_0_1 = new Version("3.0.1", 3, 0, 1, null, true); + public final static Version V3_0_2 = new Version("3.0.2", 3, 0, 2, null, true); + public final static Version V3_0_3 = new Version("3.0.3", 3, 0, 3, null, true); + public final static Version V3_0_4 = new Version("3.0.4", 3, 0, 4, null, true); + public final static Version V3_1_0 = new Version("3.1.0", 3, 1, 0, 1847, true); + public final static Version V3_1_1 = new Version("3.1.1", 3, 1, 1, 1865, true); + public final static Version V3_1_2 = new Version("3.1.2", 3, 1, 2, 2130, true); + public final static Version V3_1_3 = new Version("3.1.3", 3, 1, 3, 2398, true); + public final static Version V3_1_4 = new Version("3.1.4", 3, 1, 4, 2223, true); + public final static Version V3_1_5 = new Version("3.1.5", 3, 1, 5, 2707, true); + public final static Version V3_1_6 = new Version("3.1.6", 3, 1, 6, 2729, true); + public final static Version V3_1_7 = new Version("3.1.7", 3, 1, 7, 3085, true); + public final static Version V3_1_8 = new Version("3.1.8", 3, 1, 8, 3188, true); + public final static Version V3_1_9 = new Version("3.1.9", 3, 1, 9, 3268, true); + public final static Version V3_1_10 = new Version("3.1.10", 3, 1, 10, 3347, true); + public final static Version V3_1_11 = new Version("3.1.11", 3, 1, 11, 3557, true); + public final static Version V3_1_12 = new Version("3.1.12", 3, 1, 12, 3876, true); + public final static Version V3_1_13 = new Version("3.1.13", 3, 1, 13, 3592, true); + + private final static Map knownVersions = + Stream.of(V3_0_0, V3_0_1, V3_0_2, V3_0_3, V3_0_4, V3_1_0, V3_1_1, V3_1_2, V3_1_3, V3_1_4, V3_1_5, V3_1_6, V3_1_7, V3_1_8, V3_1_9, V3_1_10, V3_1_11, V3_1_13) + .collect(toMap(Version::toString, Function.identity())); + public final static Version LATEST = V3_1_13; + + private final String origString; + private final Integer major; + private final Integer minor; + private final Integer bugfix; + private final Integer build; + private final boolean valid; + + private Version(String origString, @Nullable Integer major, @Nullable Integer minor, @Nullable Integer bugfix, @Nullable Integer build, boolean valid) { + this.origString = origString; + this.major = major; + this.minor = minor; + this.bugfix = bugfix; + this.build = build; + this.valid = valid; + } + + /** + * Use {@link Version#create} factory method instead + * For removal + * + * @param versionString Version as String + */ + @Deprecated() + public Version(String versionString) { + Objects.requireNonNull(versionString); + + Version dummy = parseVersionString(versionString); + + this.origString = dummy.origString; + this.major = dummy.major; + this.minor = dummy.minor; + this.bugfix = dummy.bugfix; + this.build = dummy.build; + this.valid = dummy.valid; + } + + public static Version create(final String versionString) { + String origString = Objects.requireNonNull(versionString).trim(); + Version version = knownVersions.get(origString); + return version != null ? version : parseVersionString(origString); } - private void parseVersionString() - { + private static Version parseVersionString(String origString) { + + Integer major = null; + Integer minor = null; + Integer bugfix = null; + Integer build = null; + boolean valid = false; Pattern p = Pattern.compile("([0-9]+)\\.?([0-9]+)?\\.?([0-9]+)?\\.?([0-9]+)?"); Matcher m = p.matcher(origString); try { if (m.find()) { - if ( m.group(1) != null ) + if (m.group(1) != null) { major = Integer.valueOf(m.group(1)); - if ( m.group(2) != null ) + } + if (m.group(2) != null) { minor = Integer.valueOf(m.group(2)); - if ( m.group(3) != null ) + } + if (m.group(3) != null) { bugfix = Integer.valueOf(m.group(3)); - if ( m.group(4) != null ) + } + if (m.group(4) != null) { build = Integer.valueOf(m.group(4)); + } - if ( major != null ) // We need a valid major version as minimum requirement for a Version object to be valid - valid = true; + // We need a valid major version as minimum requirement for a Version object to be valid + valid = major != null; } + } catch (NumberFormatException ignore) { } - catch ( NumberFormatException e ) - { - valid = false; - } + + return new Version(origString, major, minor, bugfix, build, valid); } @Override @@ -55,18 +126,22 @@ public String toString() { return origString; } + @Nullable public Integer getMajor() { return major; } + @Nullable public Integer getMinor() { return minor; } + @Nullable public Integer getBugfix() { return bugfix; } + @Nullable public Integer getBuild() { return build; } @@ -75,118 +150,149 @@ public boolean isValid() { return valid; } - /** Returns a normalized form of the parsed version information + /** + * Returns a normalized form of the parsed version information * - * @return + * @return normalized string */ - public String getNormalizedString() - { - if ( isValid() ) { + public String getNormalizedString() { + if (isValid()) { StringBuilder sb = new StringBuilder(); - sb.append(String.valueOf(major)); - if ( minor != null ) - sb.append("." + String.valueOf(minor)); - if ( bugfix != null ) - sb.append("." + String.valueOf(bugfix)); - if ( build != null ) - sb.append("." + String.valueOf(build)); + sb.append(major); + if (minor != null) { + sb.append(".").append(minor); + } + if (bugfix != null) { + sb.append(".").append(bugfix); + } + if (build != null) { + sb.append(".").append(build); + } return sb.toString(); - } - else + } else { return "invalid"; + } } - private int compareToWithNulls( Integer i1, Integer i2 ) { - if ( i1 == null && i2 == null ) + private int compareToWithNulls(@Nullable Integer i1, @Nullable Integer i2) { + return compareToWithNulls(i1, i2, false); + } + + private int compareToWithNulls(@Nullable Integer i1, @Nullable Integer i2, boolean nullMeansEqual) { + if (i1 == null && i2 == null) { return 0; - else if ( i1 == null ) - return -1; - else if ( i2 == null ) + } else if (i1 == null) { + return nullMeansEqual ? 0 : -1; + } else if (i2 == null) { return 1; - else return i1.compareTo(i2); + } else { + return i1.compareTo(i2); + } } @Override public int compareTo(Version o) { + return compareTo(o, false); + } + + public int compareTo(Version o, boolean nullMeansEqual) { int curResult; - if ( isValid() && o.isValid() ) { + if (isValid() && o.isValid()) { - curResult = compareToWithNulls(getMajor(), o.getMajor()); - if ( curResult != 0 ) + curResult = compareToWithNulls(getMajor(), o.getMajor(), nullMeansEqual); + if (curResult != 0) { return curResult; + } - curResult = compareToWithNulls(getMinor(), o.getMinor()); - if ( curResult != 0 ) + curResult = compareToWithNulls(getMinor(), o.getMinor(), nullMeansEqual); + if (curResult != 0) { return curResult; + } - curResult = compareToWithNulls(getBugfix(), o.getBugfix()); - if ( curResult != 0 ) + curResult = compareToWithNulls(getBugfix(), o.getBugfix(), nullMeansEqual); + if (curResult != 0) { return curResult; + } - curResult = compareToWithNulls(getBuild(), o.getBuild()); - if ( curResult != 0 ) - return curResult; + curResult = compareToWithNulls(getBuild(), o.getBuild(), nullMeansEqual); + + return curResult; } return 0; } - private void versionsAreValid( Version v ) throws InvalidVersionException { - if ( !isValid() ) + private void versionsAreValid(Version v) throws InvalidVersionException { + if (!isValid()) { throw new InvalidVersionException(this); + } - if ( !v.isValid() ) + if (!v.isValid()) { throw new InvalidVersionException(v); + } } - /** Compares this version to a given version and returns true if this version is greater or equal than the given one - * Throws an InvalidVersionException if either this or the given version are invalid + /** + * Compares this version to a given version and returns true if this version is greater or equal than the given one + * If one of the version parts of the base version is null, this level is assumed equal no matter the comparing level's version part + * Throws an {@link InvalidVersionException} if either this or the given version are invalid * * @param v Version to compare with - * @return - * @throws InvalidVersionException + * @return true if is greater or equal + * @throws InvalidVersionException If the version does not match */ - public boolean isGreaterOrEqualThan( Version v ) throws InvalidVersionException { + public boolean isGreaterOrEqualThan(Version v) throws InvalidVersionException { versionsAreValid(v); - if ( compareTo(v) >= 0 ) - return true; - else - return false; + return compareTo(v, true) >= 0; } - - public boolean isGreaterThan( Version v) throws InvalidVersionException - { + /** + * Compares this version to a given version and returns true if this version is greater than the given one + * If one of the version parts of the base version is null, this level is assumed equal no matter the comparing level's version part + * Throws an {@link InvalidVersionException} if either this or the given version are invalid + * + * @param v Version to compare with + * @return true if is greater + * @throws InvalidVersionException If the version does not match + */ + public boolean isGreaterThan(Version v) throws InvalidVersionException { versionsAreValid(v); - if ( compareTo(v) > 0 ) - return true; - else - return false; + return compareTo(v) > 0; } - public boolean isLessOrEqualThan( Version v ) throws InvalidVersionException - { + /** + * Compares this version to a given version and returns true if this version is less or equal than the given one + * If one of the version parts of the base version is null, this level is assumed equal no matter the comparing level's version part + * Throws an {@link InvalidVersionException} if either this or the given version are invalid + * + * @param v Version to compare with + * @return if version is less or equal + * @throws InvalidVersionException If version is invalid + */ + public boolean isLessOrEqualThan(Version v) throws InvalidVersionException { versionsAreValid(v); - if ( compareTo(v) <= 0 ) - return true; - else - return false; + return compareTo(v, true) <= 0; } - public boolean isLessThan( Version v) throws InvalidVersionException - { + /** + * Compares this version to a given version and returns true if this version is less than the given one + * If one of the version parts of the base version is null, this level is assumed equal no matter the comparing level's version part + * Throws an {@link InvalidVersionException} if either this or the given version are invalid + * + * @param v Version to compare with + * @return if version is less + * @throws InvalidVersionException If version is invalid + */ + public boolean isLessThan(Version v) throws InvalidVersionException { versionsAreValid(v); - if ( compareTo(v) < 0 ) - return true; - else - return false; + return compareTo(v) < 0; } } diff --git a/src/main/java/org/utplsql/api/compatibility/CompatibilityProxy.java b/src/main/java/org/utplsql/api/compatibility/CompatibilityProxy.java index bdeb7c4..eb68f23 100644 --- a/src/main/java/org/utplsql/api/compatibility/CompatibilityProxy.java +++ b/src/main/java/org/utplsql/api/compatibility/CompatibilityProxy.java @@ -1,18 +1,23 @@ package org.utplsql.api.compatibility; -import org.utplsql.api.DBHelper; import org.utplsql.api.TestRunnerOptions; import org.utplsql.api.Version; +import org.utplsql.api.db.DatabaseInformation; +import org.utplsql.api.db.DefaultDatabaseInformation; import org.utplsql.api.exception.DatabaseNotCompatibleException; +import org.utplsql.api.outputBuffer.OutputBuffer; +import org.utplsql.api.outputBuffer.OutputBufferProvider; +import org.utplsql.api.reporter.Reporter; import org.utplsql.api.testRunner.TestRunnerStatement; import org.utplsql.api.testRunner.TestRunnerStatementProvider; -import java.sql.CallableStatement; +import javax.annotation.Nullable; import java.sql.Connection; import java.sql.SQLException; -import java.sql.Types; +import java.util.Objects; -/** Class to check compatibility with database framework and also to give several specific implementations depending +/** + * Class to check compatibility with database framework and also to give several specific implementations depending * on the version of the connected framework. * If one skips the compatibility check, the Proxy acts as like the framework has the same version as the API * @@ -20,130 +25,171 @@ */ public class CompatibilityProxy { - public static final String UTPLSQL_API_VERSION = "3.0.4"; - public static final String UTPLSQL_COMPATIBILITY_VERSION = "3.0"; - - private Version databaseVersion; + public static final String UTPLSQL_COMPATIBILITY_VERSION = "3"; + private final DatabaseInformation databaseInformation; + private Version utPlsqlVersion; + private final Version realDbPlsqlVersion; private boolean compatible = false; - public CompatibilityProxy( Connection conn ) throws SQLException - { - this(conn, false); + public CompatibilityProxy(Connection conn) throws SQLException { + this(conn, null, null); + } + + @Deprecated + public CompatibilityProxy(Connection conn, boolean skipCompatibilityCheck) throws SQLException { + this(conn, skipCompatibilityCheck, null); } - public CompatibilityProxy( Connection conn, boolean skipCompatibilityCheck ) throws SQLException - { - if ( skipCompatibilityCheck ) - doExpectCompatibility(); - else + @Deprecated + public CompatibilityProxy(Connection conn, boolean skipCompatibilityCheck, @Nullable DatabaseInformation databaseInformation) throws SQLException { + this(conn, skipCompatibilityCheck ? Version.LATEST : null, databaseInformation); + } + + public CompatibilityProxy(Connection conn, @Nullable DatabaseInformation databaseInformation) throws SQLException { + this(conn, null, databaseInformation); + } + + public CompatibilityProxy(Connection conn, @Nullable Version assumedUtPlsVersion) throws SQLException { + this(conn, assumedUtPlsVersion, null); + } + + public CompatibilityProxy(Connection conn, @Nullable Version assumedUtPlsqlVersion, @Nullable DatabaseInformation databaseInformation) throws SQLException { + this.databaseInformation = (databaseInformation != null) + ? databaseInformation + : new DefaultDatabaseInformation(); + + realDbPlsqlVersion = this.databaseInformation.getUtPlsqlFrameworkVersion(conn); + if (assumedUtPlsqlVersion != null) { + utPlsqlVersion = assumedUtPlsqlVersion; + compatible = utPlsqlVersion.getNormalizedString().startsWith(UTPLSQL_COMPATIBILITY_VERSION); + } else { doCompatibilityCheckWithDatabase(conn); + } } - /** Receives the current framework version from database and checks - depending on the framework version - whether + /** + * Receives the current framework version from database and checks - depending on the framework version - whether * the API version is compatible or not. * - * @param conn - * @throws SQLException + * @param conn {@link Connection} + * @throws DatabaseNotCompatibleException if the database is not compatible */ - private void doCompatibilityCheckWithDatabase( Connection conn ) throws SQLException - { - databaseVersion = DBHelper.getDatabaseFrameworkVersion(conn); + private void doCompatibilityCheckWithDatabase(Connection conn) throws DatabaseNotCompatibleException { + utPlsqlVersion = realDbPlsqlVersion; + Version clientVersion = Version.create(UTPLSQL_COMPATIBILITY_VERSION); + + if (utPlsqlVersion == null) { + throw new DatabaseNotCompatibleException("Could not get database version", clientVersion, null, null); + } + + if (utPlsqlVersion.getMajor() == null) { + throw new DatabaseNotCompatibleException("Illegal database version: " + utPlsqlVersion.toString(), clientVersion, utPlsqlVersion, null); + } - if (OptionalFeatures.FRAMEWORK_COMPATIBILITY_CHECK.isAvailableFor(databaseVersion)) { + if (OptionalFeatures.FRAMEWORK_COMPATIBILITY_CHECK.isAvailableFor(utPlsqlVersion)) { try { compatible = versionCompatibilityCheck(conn, UTPLSQL_COMPATIBILITY_VERSION, null); } catch (SQLException e) { - throw new DatabaseNotCompatibleException("Compatibility-check failed with error. Aborting. Reason: " + e.getMessage(), new Version(UTPLSQL_COMPATIBILITY_VERSION), new Version("Unknown"), e); + throw new DatabaseNotCompatibleException("Compatibility-check failed with error. Aborting. Reason: " + e.getMessage(), clientVersion, Version.create("Unknown"), e); } - } else + } else { compatible = versionCompatibilityCheckPre303(UTPLSQL_COMPATIBILITY_VERSION); - } - - /** Just prepare the proxy to expect compatibility, expecting the database framework to be the same version as the API - * - */ - private void doExpectCompatibility() - { - databaseVersion = new Version(UTPLSQL_API_VERSION); - compatible = true; + } } /** * Check the utPLSQL version compatibility. + * * @param conn the connection * @return true if the requested utPLSQL version is compatible with the one installed on database * @throws SQLException any database error */ private boolean versionCompatibilityCheck(Connection conn, String requested, String current) throws SQLException { - CallableStatement callableStatement = null; try { - callableStatement = conn.prepareCall("BEGIN ? := ut_runner.version_compatibility_check(?, ?); END;"); - callableStatement.registerOutParameter(1, Types.SMALLINT); - callableStatement.setString(2, requested); - - if (current == null) - callableStatement.setNull(3, Types.VARCHAR); - else - callableStatement.setString(3, current); - - callableStatement.executeUpdate(); - return callableStatement.getInt(1) == 1; + return databaseInformation.frameworkCompatibilityCheck(conn, requested, current) == 1; } catch (SQLException e) { - if (e.getErrorCode() == 6550) + if (e.getErrorCode() == 6550) { return false; - else + } else { throw e; - } finally { - if (callableStatement != null) - callableStatement.close(); + } } } - /** Simple fallback check for compatiblity: Major and Minor version must be equal + /** + * Simple fallback check for compatibility: Major and Minor version must be equal * - * @param requested - * @return + * @param versionRequested Requested version + * @return weather the version is available or not */ - private boolean versionCompatibilityCheckPre303( String requested ) - { - Version requesteVersion = new Version(requested); + private boolean versionCompatibilityCheckPre303(String versionRequested) { + Version requestedVersion = Version.create(versionRequested); - if ( databaseVersion.getMajor() == requesteVersion.getMajor() && (requesteVersion.getMinor() == null || databaseVersion.getMinor() == requesteVersion.getMinor()) ) - return true; - else - return false; + Objects.requireNonNull(utPlsqlVersion.getMajor(), "Illegal database Version: " + utPlsqlVersion.toString()); + return Objects.equals(utPlsqlVersion.getMajor(), requestedVersion.getMajor()) + && (requestedVersion.getMinor() == null + || requestedVersion.getMinor().equals(utPlsqlVersion.getMinor())); } - /** Checks if actual API-version is compatible with utPLSQL database version and throws a DatabaseNotCompatibleException if not + /** + * Checks if actual API-version is compatible with utPLSQL database version and throws a DatabaseNotCompatibleException if not * Throws a DatabaseNotCompatibleException if version compatibility can not be checked. * + * @throws DatabaseNotCompatibleException if versions are not compatible */ - public void failOnNotCompatible() throws DatabaseNotCompatibleException - { - if ( !isCompatible() ) - throw new DatabaseNotCompatibleException( databaseVersion ); + public void failOnNotCompatible() throws DatabaseNotCompatibleException { + if (!isCompatible()) { + throw new DatabaseNotCompatibleException(utPlsqlVersion); + } } - public boolean isCompatible() - { + public boolean isCompatible() { return compatible; } - public Version getDatabaseVersion() - { - return databaseVersion; + @Deprecated + public Version getDatabaseVersion() { + return utPlsqlVersion; + } + + public Version getUtPlsqlVersion() { + return utPlsqlVersion; + } + + public Version getRealDbPlsqlVersion() { + return realDbPlsqlVersion; + } + + public String getVersionDescription() { + if (utPlsqlVersion != realDbPlsqlVersion) { + return realDbPlsqlVersion.toString() + " (Assumed: " + utPlsqlVersion.toString() + ")"; + } else { + return utPlsqlVersion.toString(); + } + } + + /** + * Returns a TestRunnerStatement compatible with the current framework + * + * @param options {@link TestRunnerOptions} + * @param conn {@link Connection} + * @return TestRunnerStatement + * @throws SQLException if there are problems with the database access + */ + public TestRunnerStatement getTestRunnerStatement(TestRunnerOptions options, Connection conn) throws SQLException { + return TestRunnerStatementProvider.getCompatibleTestRunnerStatement(utPlsqlVersion, options, conn); } - /** Returns a TestRunnerStatement compatible with the current framework + /** + * Returns an OutputBuffer compatible with the current framework * - * @param options - * @param conn - * @return - * @throws SQLException + * @param reporter {@link Reporter} + * @param conn {@link Connection} + * @return OutputBuffer + * @throws SQLException if there are problems with the database access */ - public TestRunnerStatement getTestRunnerStatement(TestRunnerOptions options, Connection conn) throws SQLException - { - return TestRunnerStatementProvider.getCompatibleTestRunnerStatement(databaseVersion, options, conn); + public OutputBuffer getOutputBuffer(Reporter reporter, Connection conn) throws SQLException { + return OutputBufferProvider.getCompatibleOutputBuffer(utPlsqlVersion, reporter, conn); } } diff --git a/src/main/java/org/utplsql/api/compatibility/OptionalFeatures.java b/src/main/java/org/utplsql/api/compatibility/OptionalFeatures.java index c82e9c1..8462b95 100644 --- a/src/main/java/org/utplsql/api/compatibility/OptionalFeatures.java +++ b/src/main/java/org/utplsql/api/compatibility/OptionalFeatures.java @@ -3,33 +3,43 @@ import org.utplsql.api.Version; import org.utplsql.api.exception.InvalidVersionException; +import java.sql.Connection; +import java.sql.SQLException; + public enum OptionalFeatures { - FAIL_ON_ERROR("3.0.3", null), - FRAMEWORK_COMPATIBILITY_CHECK("3.0.3", null); + FAIL_ON_ERROR("3.0.3.1266", null), + FRAMEWORK_COMPATIBILITY_CHECK("3.0.3.1266", null), + CUSTOM_REPORTERS("3.1.0.1849", null), + CLIENT_CHARACTER_SET("3.1.2.2130", null), + RANDOM_EXECUTION_ORDER("3.1.7.2795", null), + TAGS("3.1.7.3006", null), + EXPR("3.1.13", null); - private Version minVersion; - private Version maxVersion; + private final Version minVersion; + private final Version maxVersion; - OptionalFeatures( String minVersion, String maxVersion ) - { - if ( minVersion != null ) - this.minVersion = new Version(minVersion); - if ( maxVersion != null) - this.maxVersion = new Version(maxVersion); + OptionalFeatures(String minVersion, String maxVersion) { + this.minVersion = minVersion != null ? Version.create(minVersion) : null; + this.maxVersion = maxVersion != null ? Version.create(maxVersion) : null; } - public boolean isAvailableFor(Version version ) { + public boolean isAvailableFor(Version version) { try { - if ((minVersion == null || version.isGreaterOrEqualThan(minVersion)) && - (maxVersion == null || maxVersion.isGreaterOrEqualThan(version)) - ) - return true; - else - return false; - } catch ( InvalidVersionException e ) { + return (minVersion == null || version.isGreaterOrEqualThan(minVersion)) && + (maxVersion == null || maxVersion.isGreaterOrEqualThan(version)); + } catch (InvalidVersionException e) { return false; // We have no optional features for invalid versions } } + + public boolean isAvailableFor(Connection conn) throws SQLException { + CompatibilityProxy proxy = new CompatibilityProxy(conn); + return isAvailableFor(proxy.getUtPlsqlVersion()); + } + + public Version getMinVersion() { return minVersion; } + + public Version getMaxVersion() { return maxVersion; } } diff --git a/src/main/java/org/utplsql/api/db/DatabaseInformation.java b/src/main/java/org/utplsql/api/db/DatabaseInformation.java new file mode 100644 index 0000000..6e76cfa --- /dev/null +++ b/src/main/java/org/utplsql/api/db/DatabaseInformation.java @@ -0,0 +1,23 @@ +package org.utplsql.api.db; + +import org.utplsql.api.Version; + +import javax.annotation.Nullable; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Abstraction-interface to encapsulate Database-Calls (and potentially mock them) + * + * @author pesse + */ +public interface DatabaseInformation { + + Version getUtPlsqlFrameworkVersion(Connection conn) throws SQLException; + + String getOracleVersion(Connection conn) throws SQLException; + + String getCurrentSchema(Connection conn) throws SQLException; + + int frameworkCompatibilityCheck(Connection conn, String requested, @Nullable String current) throws SQLException; +} diff --git a/src/main/java/org/utplsql/api/db/DefaultDatabaseInformation.java b/src/main/java/org/utplsql/api/db/DefaultDatabaseInformation.java new file mode 100644 index 0000000..181ffa6 --- /dev/null +++ b/src/main/java/org/utplsql/api/db/DefaultDatabaseInformation.java @@ -0,0 +1,72 @@ +package org.utplsql.api.db; + +import org.utplsql.api.Version; +import org.utplsql.api.exception.UtPLSQLNotInstalledException; + +import javax.annotation.Nullable; +import java.sql.*; + +public class DefaultDatabaseInformation implements DatabaseInformation { + + @Override + public Version getUtPlsqlFrameworkVersion(Connection conn) throws SQLException { + Version result = Version.create(""); + try (PreparedStatement stmt = conn.prepareStatement("select ut_runner.version() from dual")) { + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) { + result = Version.create(rs.getString(1)); + } + + rs.close(); + } catch (SQLException e) { + if (e.getErrorCode() == UtPLSQLNotInstalledException.ERROR_CODE) { + throw new UtPLSQLNotInstalledException(e); + } else { + throw e; + } + } + + return result; + } + + @Override + public String getOracleVersion(Connection conn) throws SQLException { + String result = null; + try (PreparedStatement stmt = conn.prepareStatement("select version from product_component_version where product like 'Oracle Database%'")) { + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) { + result = rs.getString(1); + } + } + + return result; + } + + @Override + public String getCurrentSchema(Connection conn) throws SQLException { + try (CallableStatement callableStatement = conn.prepareCall("BEGIN ? := sys_context('userenv', 'current_schema'); END;")) { + callableStatement.registerOutParameter(1, Types.VARCHAR); + callableStatement.executeUpdate(); + return callableStatement.getString(1); + } + } + + @Override + public int frameworkCompatibilityCheck(Connection conn, String requested, @Nullable String current) throws SQLException { + try (CallableStatement callableStatement = conn.prepareCall("BEGIN ? := ut_runner.version_compatibility_check(?, ?); END;")) { + callableStatement.registerOutParameter(1, Types.SMALLINT); + callableStatement.setString(2, requested); + + if (current == null) { + callableStatement.setNull(3, Types.VARCHAR); + } else { + callableStatement.setString(3, current); + } + + callableStatement.executeUpdate(); + return callableStatement.getInt(1); + } + } +} diff --git a/src/main/java/org/utplsql/api/db/DynamicParameterList.java b/src/main/java/org/utplsql/api/db/DynamicParameterList.java new file mode 100644 index 0000000..c6df04c --- /dev/null +++ b/src/main/java/org/utplsql/api/db/DynamicParameterList.java @@ -0,0 +1,220 @@ +package org.utplsql.api.db; + +import oracle.jdbc.OracleConnection; + +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.LinkedHashMap; +import java.util.stream.Collectors; + +/** Lets you build a list of parameters for a CallableStatement + *
+ * Create it with the Builder (DynamicParameterList.builder()) + * + * @author pesse + */ +public class DynamicParameterList { + + private final LinkedHashMap params; + + interface DynamicParameter { + void setParam( CallableStatement statement, int index ) throws SQLException; + + default String getSql( String key ) { + return key + " => ?"; + } + } + + private DynamicParameterList(LinkedHashMap params) { + this.params = params; + } + + /** Returns the SQL of this ParameterList as comma-separated list of the parameter identifiers:
+ * + * e.g. "a_parameter1 => ?, a_parameter2 => ?" + * + * @return comma-separated list of parameter identifiers + */ + public String getSql() { + return params.entrySet().stream() + .map(e -> e.getValue().getSql(e.getKey())) + .collect(Collectors.joining(", ")); + } + + /** Sets the contained parameters in the order they were added to the given statement by index, starting with the given one + * + * @param statement The statement to set the parameters to + * @param startIndex The index to start with + * @throws SQLException SQLException of the underlying statement.setX methods + */ + public void setParamsStartWithIndex(CallableStatement statement, int startIndex ) throws SQLException { + int index = startIndex; + for ( DynamicParameter param : params.values() ) { + param.setParam(statement, index++); + } + } + + /** Returns a builder to create a DynamicParameterList + * + * @return Builder + */ + public static DynamicParameterListBuilder builder() { + return new DynamicParameterListBuilder(); + } + + /** Builder-class for DynamicParameterList + *
+ * Usage: + *

+     *  DynamicParameterList.builder()
+     *      .add("parameter1", "StringParameter")
+     *      .addIfNotEmpty("parameter2", 123)
+     *      .build();
+     * 
+ * + * @author pesse + */ + public static class DynamicParameterListBuilder { + + private final LinkedHashMap params = new LinkedHashMap<>(); + + private DynamicParameterListBuilder() { + + } + + public DynamicParameterListBuilder add(String identifier, String value ) { + params.put(identifier, new DynamicParameterList.DynamicStringParameter(value)); + return this; + } + + public DynamicParameterListBuilder addIfNotEmpty(String identifier, String value ) { + if ( value != null && !value.isEmpty() ) { + add(identifier, value); + } + return this; + } + + public DynamicParameterListBuilder add(String identifier, Integer value ) { + params.put(identifier, new DynamicParameterList.DynamicIntegerParameter(value)); + return this; + } + + public DynamicParameterListBuilder addIfNotEmpty(String identifier, Integer value ) { + if ( value != null) { + add(identifier, value); + } + return this; + } + + public DynamicParameterListBuilder add(String identifier, Object[] value, String customTypeName, OracleConnection oraConnection ) { + params.put(identifier, new DynamicParameterList.DynamicArrayParameter(value, customTypeName, oraConnection)); + return this; + } + + public DynamicParameterListBuilder addIfNotEmpty(String identifier, Object[] value, String customTypeName, OracleConnection oraConnection ) { + if ( value != null && value.length > 0 ) { + add(identifier, value, customTypeName, oraConnection); + } + return this; + } + + public DynamicParameterListBuilder add(String identifier, Boolean value) { + params.put(identifier, new DynamicBoolParameter(value)); + return this; + } + + public DynamicParameterListBuilder addIfNotEmpty(String identifier, Boolean value) { + if ( value != null ) { + add(identifier, value); + } + return this; + } + + public DynamicParameterList build() { + return new DynamicParameterList(params); + } + } + + /* Implementations of DynamicStringParameter */ + private static class DynamicStringParameter implements DynamicParameter { + private final String value; + + DynamicStringParameter( String value ) { + this.value = value; + } + + @Override + public void setParam(CallableStatement statement, int index) throws SQLException { + if ( value == null ) { + statement.setNull(index, Types.VARCHAR); + } else { + statement.setString(index, value); + } + } + } + + private static class DynamicIntegerParameter implements DynamicParameter { + private final Integer value; + + DynamicIntegerParameter( Integer value ) { + this.value = value; + } + + @Override + public void setParam(CallableStatement statement, int index) throws SQLException { + if ( value == null ) { + statement.setNull(index, Types.INTEGER); + } else { + statement.setInt(index, value); + } + } + } + + private static class DynamicBoolParameter implements DynamicParameter { + private final Boolean value; + + DynamicBoolParameter( Boolean value ) { + this.value = value; + } + + @Override + public void setParam(CallableStatement statement, int index) throws SQLException { + if ( value == null ) { + statement.setNull(index, Types.BOOLEAN); + } else { + statement.setInt(index, (value)?1:0); + } + } + + @Override + public String getSql(String key) { + return key + " => (case ? when 1 then true else false end)"; + } + } + + private static class DynamicArrayParameter implements DynamicParameter { + private final Object[] value; + private final String customTypeName; + private final OracleConnection oraConnection; + + DynamicArrayParameter( Object[] value, String customTypeName, OracleConnection oraConnection ) { + this.value = value; + this.customTypeName = customTypeName; + this.oraConnection = oraConnection; + } + + @Override + public void setParam(CallableStatement statement, int index) throws SQLException { + if ( value == null ) { + statement.setNull(index, Types.ARRAY, customTypeName); + } else { + statement.setArray( + index, + oraConnection.createOracleArray(customTypeName, value) + ); + } + } + } + +} diff --git a/src/main/java/org/utplsql/api/exception/DatabaseNotCompatibleException.java b/src/main/java/org/utplsql/api/exception/DatabaseNotCompatibleException.java index 5d944d6..b7b7111 100644 --- a/src/main/java/org/utplsql/api/exception/DatabaseNotCompatibleException.java +++ b/src/main/java/org/utplsql/api/exception/DatabaseNotCompatibleException.java @@ -1,55 +1,48 @@ package org.utplsql.api.exception; -import org.utplsql.api.DBHelper; import org.utplsql.api.Version; import org.utplsql.api.compatibility.CompatibilityProxy; import java.sql.SQLException; -/** Custom exception to indicate API is not compatible with database framework +/** + * Custom exception to indicate API is not compatible with database framework * * @author pesse - * */ public class DatabaseNotCompatibleException extends SQLException { - private Version clientVersion; - private Version databaseVersion; + private final Version clientVersion; + private final Version databaseVersion; - public DatabaseNotCompatibleException( String message, Version clientVersion, Version databaseVersion, Throwable cause ) - { + public DatabaseNotCompatibleException(String message, Version clientVersion, Version databaseVersion, Throwable cause) { super(message, cause); this.clientVersion = clientVersion; this.databaseVersion = databaseVersion; } - public DatabaseNotCompatibleException( Version clientVersion, Version databaseVersion, Throwable cause ) - { - this("utPLSQL API (" + String.valueOf(clientVersion) + ") not compatible with database (" + String.valueOf(databaseVersion) + ")", clientVersion, databaseVersion, cause); + public DatabaseNotCompatibleException(Version clientVersion, Version databaseVersion, Throwable cause) { + this("utPLSQL API (" + clientVersion + ") not compatible with database (" + databaseVersion + ")", clientVersion, databaseVersion, cause); } - public DatabaseNotCompatibleException( Version clientVersion, Version databaseVersion ) - { + public DatabaseNotCompatibleException(Version clientVersion, Version databaseVersion) { this(clientVersion, databaseVersion, null); } - public DatabaseNotCompatibleException( Version databaseVersion, Throwable cause ) - { - this(new Version(CompatibilityProxy.UTPLSQL_COMPATIBILITY_VERSION), databaseVersion, cause ); + public DatabaseNotCompatibleException(Version databaseVersion, Throwable cause) { + this(Version.create(CompatibilityProxy.UTPLSQL_COMPATIBILITY_VERSION), databaseVersion, cause); } - public DatabaseNotCompatibleException( Version databaseVersion ) - { - this(new Version(CompatibilityProxy.UTPLSQL_COMPATIBILITY_VERSION), databaseVersion, null ); + public DatabaseNotCompatibleException(Version databaseVersion) { + this(Version.create(CompatibilityProxy.UTPLSQL_COMPATIBILITY_VERSION), databaseVersion, null); } public Version getClientVersion() { return clientVersion; } - public Version getDatabaseVersion() - { + public Version getDatabaseVersion() { return databaseVersion; } } diff --git a/src/main/java/org/utplsql/api/exception/InvalidVersionException.java b/src/main/java/org/utplsql/api/exception/InvalidVersionException.java index f9a6d59..3350e7d 100644 --- a/src/main/java/org/utplsql/api/exception/InvalidVersionException.java +++ b/src/main/java/org/utplsql/api/exception/InvalidVersionException.java @@ -2,19 +2,19 @@ import org.utplsql.api.Version; -/** Exception thrown when trying to do stuff which requires a valid version object (like comparing) +/** + * Exception thrown when trying to do stuff which requires a valid version object (like comparing) * * @author pesse */ public class InvalidVersionException extends Exception { - private Version version; + private final Version version; - public InvalidVersionException( Version version ) { - this( version, null ); + public InvalidVersionException(Version version) { + this(version, null); } - public InvalidVersionException( Version version, Throwable cause ) - { + public InvalidVersionException(Version version, Throwable cause) { super("Version '" + version.toString() + "' is invalid", cause); this.version = version; diff --git a/src/main/java/org/utplsql/api/exception/OracleCreateStatmenetStuckException.java b/src/main/java/org/utplsql/api/exception/OracleCreateStatmenetStuckException.java new file mode 100644 index 0000000..6183638 --- /dev/null +++ b/src/main/java/org/utplsql/api/exception/OracleCreateStatmenetStuckException.java @@ -0,0 +1,9 @@ +package org.utplsql.api.exception; + +import java.sql.SQLException; + +public class OracleCreateStatmenetStuckException extends SQLException { + public OracleCreateStatmenetStuckException(Throwable cause) { + super("Oracle driver stuck during creating the TestRunner statement. Retry.", cause); + } +} diff --git a/src/main/java/org/utplsql/api/exception/SomeTestsFailedException.java b/src/main/java/org/utplsql/api/exception/SomeTestsFailedException.java index 708bad6..a7f8ffe 100644 --- a/src/main/java/org/utplsql/api/exception/SomeTestsFailedException.java +++ b/src/main/java/org/utplsql/api/exception/SomeTestsFailedException.java @@ -1,5 +1,6 @@ package org.utplsql.api.exception; +import javax.annotation.Nullable; import java.sql.SQLException; /** @@ -9,7 +10,7 @@ public class SomeTestsFailedException extends SQLException { public static final int ERROR_CODE = 20213; - public SomeTestsFailedException(String reason, Throwable cause) { + public SomeTestsFailedException(String reason, @Nullable Throwable cause) { super(reason, cause); } diff --git a/src/main/java/org/utplsql/api/exception/UtPLSQLNotInstalledException.java b/src/main/java/org/utplsql/api/exception/UtPLSQLNotInstalledException.java index 0e218a3..fa197ef 100644 --- a/src/main/java/org/utplsql/api/exception/UtPLSQLNotInstalledException.java +++ b/src/main/java/org/utplsql/api/exception/UtPLSQLNotInstalledException.java @@ -2,7 +2,8 @@ import java.sql.SQLException; -/** Exception to track when utPLSQL framework is not installed or accessible on the used database +/** + * Exception to track when utPLSQL framework is not installed or accessible on the used database * * @author pesse */ @@ -10,7 +11,7 @@ public class UtPLSQLNotInstalledException extends SQLException { public static final int ERROR_CODE = 904; - public UtPLSQLNotInstalledException( SQLException cause ) { + public UtPLSQLNotInstalledException(SQLException cause) { super("utPLSQL framework is not installed on your database or not accessable to the user you are connected with", cause); } } diff --git a/src/main/java/org/utplsql/api/outputBuffer/AbstractOutputBuffer.java b/src/main/java/org/utplsql/api/outputBuffer/AbstractOutputBuffer.java new file mode 100644 index 0000000..5354db6 --- /dev/null +++ b/src/main/java/org/utplsql/api/outputBuffer/AbstractOutputBuffer.java @@ -0,0 +1,129 @@ +package org.utplsql.api.outputBuffer; + +import org.utplsql.api.reporter.Reporter; + +import java.io.PrintStream; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Fetches the lines produced by a reporter. + * + * @author vinicius + * @author pesse + */ +abstract class AbstractOutputBuffer implements OutputBuffer { + + private final Reporter reporter; + private int fetchSize = 100; + + /** + * Creates a new DefaultOutputBuffer. + * + * @param reporter the reporter to be used + */ + AbstractOutputBuffer(Reporter reporter) { + + assert reporter.isInit() : "Reporter is not initialized! You can only create OutputBuffers for initialized Reporters"; + + this.reporter = reporter; + } + + /** + * Returns the reporter used by this buffer. + * + * @return the reporter instance + */ + public Reporter getReporter() { + return reporter; + } + + @Override + public OutputBuffer setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + return this; + } + + /** + * Print the lines as soon as they are produced and write to a PrintStream. + * + * @param conn DB connection + * @param ps the PrintStream to be used, e.g: System.out + * @throws SQLException any sql errors + */ + public void printAvailable(Connection conn, PrintStream ps) throws SQLException { + List printStreams = new ArrayList<>(1); + printStreams.add(ps); + printAvailable(conn, printStreams); + } + + /** + * Print the lines as soon as they are produced and write to a list of PrintStreams. + * + * @param conn DB connection + * @param printStreams the PrintStream list to be used, e.g: System.out, new PrintStream(new FileOutputStream) + * @throws SQLException any sql errors + */ + public void printAvailable(Connection conn, List printStreams) throws SQLException { + fetchAvailable(conn, s -> { + for (PrintStream ps : printStreams) { + ps.println(s); + } + }); + } + + protected abstract CallableStatement getLinesCursorStatement(Connection conn) throws SQLException; + + /** + * Print the lines as soon as they are produced and call the callback passing the new line. + * + * @param conn DB connection + * @param onLineFetched the callback to be called + * @throws SQLException any sql errors + */ + public void fetchAvailable(Connection conn, Consumer onLineFetched) throws SQLException { + + try (CallableStatement cstmt = getLinesCursorStatement(conn)) { + cstmt.execute(); + cstmt.setFetchSize(fetchSize); + + try (ResultSet resultSet = (ResultSet) cstmt.getObject(1)) { + while (resultSet.next()) { + onLineFetched.accept(resultSet.getString("text")); + } + } + } + } + + /** + * Get all lines from output buffer and return it as a list of strings. + * + * @param conn DB connection + * @return the lines + * @throws SQLException any sql errors + */ + public List fetchAll(Connection conn) throws SQLException { + + try (CallableStatement cstmt = getLinesCursorStatement(conn)) { + + cstmt.execute(); + cstmt.setFetchSize(fetchSize); + + try (ResultSet resultSet = (ResultSet) cstmt.getObject(1)) { + + List outputLines = new ArrayList<>(); + while (resultSet.next()) { + outputLines.add(resultSet.getString("text")); + } + return outputLines; + } + } + } + + +} diff --git a/src/main/java/org/utplsql/api/outputBuffer/CompatibilityOutputBufferPre310.java b/src/main/java/org/utplsql/api/outputBuffer/CompatibilityOutputBufferPre310.java new file mode 100644 index 0000000..423ed03 --- /dev/null +++ b/src/main/java/org/utplsql/api/outputBuffer/CompatibilityOutputBufferPre310.java @@ -0,0 +1,28 @@ +package org.utplsql.api.outputBuffer; + +import oracle.jdbc.OracleTypes; +import org.utplsql.api.reporter.Reporter; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Compatibility Output-Buffer for 3.0.0 - 3.0.4 + * + * @author pesse + */ +class CompatibilityOutputBufferPre310 extends AbstractOutputBuffer { + + CompatibilityOutputBufferPre310(Reporter reporter) { + super(reporter); + } + + @Override + protected CallableStatement getLinesCursorStatement(Connection conn) throws SQLException { + CallableStatement cstmt = conn.prepareCall("BEGIN ? := ut_output_buffer.get_lines_cursor(?); END;"); + cstmt.registerOutParameter(1, OracleTypes.CURSOR); + cstmt.setString(2, getReporter().getId()); + return cstmt; + } +} diff --git a/src/main/java/org/utplsql/api/outputBuffer/DefaultOutputBuffer.java b/src/main/java/org/utplsql/api/outputBuffer/DefaultOutputBuffer.java new file mode 100644 index 0000000..ccf829c --- /dev/null +++ b/src/main/java/org/utplsql/api/outputBuffer/DefaultOutputBuffer.java @@ -0,0 +1,37 @@ +package org.utplsql.api.outputBuffer; + +import oracle.jdbc.OracleCallableStatement; +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OracleTypes; +import org.utplsql.api.reporter.Reporter; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Fetches the lines produced by a reporter. + * + * @author vinicius + * @author pesse + */ +class DefaultOutputBuffer extends AbstractOutputBuffer { + + /** + * Creates a new DefaultOutputBuffer. + * + * @param reporter the reporter to be used + */ + DefaultOutputBuffer(Reporter reporter) { + super(reporter); + } + + @Override + protected CallableStatement getLinesCursorStatement(Connection conn) throws SQLException { + OracleConnection oraConn = conn.unwrap(OracleConnection.class); + OracleCallableStatement cstmt = (OracleCallableStatement) oraConn.prepareCall("{? = call ?.get_lines_cursor() }"); + cstmt.registerOutParameter(1, OracleTypes.CURSOR); + cstmt.setORAData(2, getReporter()); + return cstmt; + } +} diff --git a/src/main/java/org/utplsql/api/outputBuffer/NonOutputBuffer.java b/src/main/java/org/utplsql/api/outputBuffer/NonOutputBuffer.java new file mode 100644 index 0000000..971cd63 --- /dev/null +++ b/src/main/java/org/utplsql/api/outputBuffer/NonOutputBuffer.java @@ -0,0 +1,59 @@ +package org.utplsql.api.outputBuffer; + +import org.utplsql.api.reporter.Reporter; + +import java.io.PrintStream; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * An OutputBuffer replacement which just returns nothing at all. Suitable for Reporters without any output + * + * @author pesse + */ +class NonOutputBuffer implements OutputBuffer { + + private final Reporter reporter; + + NonOutputBuffer(Reporter reporter) { + this.reporter = reporter; + } + + @Override + public Reporter getReporter() { + return reporter; + } + + @Override + public OutputBuffer setFetchSize(int fetchSize) { + return this; + } + + @Override + public void printAvailable(Connection conn, PrintStream ps) { + List printStreams = new ArrayList<>(1); + printStreams.add(ps); + printAvailable(conn, printStreams); + } + + @Override + public void printAvailable(Connection conn, List printStreams) { + fetchAvailable(conn, s -> { + for (PrintStream ps : printStreams) { + ps.println(s); + } + }); + } + + @Override + public void fetchAvailable(Connection conn, Consumer onLineFetched) { + onLineFetched.accept(null); + } + + @Override + public List fetchAll(Connection conn) { + return new ArrayList<>(); + } +} diff --git a/src/main/java/org/utplsql/api/outputBuffer/OutputBuffer.java b/src/main/java/org/utplsql/api/outputBuffer/OutputBuffer.java new file mode 100644 index 0000000..6b2bcbd --- /dev/null +++ b/src/main/java/org/utplsql/api/outputBuffer/OutputBuffer.java @@ -0,0 +1,59 @@ +package org.utplsql.api.outputBuffer; + +import org.utplsql.api.reporter.Reporter; + +import java.io.PrintStream; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.function.Consumer; + +public interface OutputBuffer { + + Reporter getReporter(); + + /** + * Override the fetchSize of the OutputBuffer + * + * @param fetchSize the ResultSet fetch-size. + * @return this Output-Buffer + */ + OutputBuffer setFetchSize(int fetchSize); + + /** + * Print the lines as soon as they are produced and write to a PrintStream. + * + * @param conn DB connection + * @param ps the PrintStream to be used, e.g: System.out + * @throws SQLException any sql errors + */ + void printAvailable(Connection conn, PrintStream ps) throws SQLException; + + /** + * Print the lines as soon as they are produced and write to a list of PrintStreams. + * + * @param conn DB connection + * @param printStreams the PrintStream list to be used, e.g: System.out, new PrintStream(new FileOutputStream) + * @throws SQLException any sql errors + */ + void printAvailable(Connection conn, List printStreams) throws SQLException; + + /** + * Print the lines as soon as they are produced and call the callback passing the new line. + * + * @param conn DB connection + * @param onLineFetched the callback to be called + * @throws SQLException any sql errors + */ + void fetchAvailable(Connection conn, Consumer onLineFetched) throws SQLException; + + /** + * Get all lines from output buffer and return it as a list of strings. + * + * @param conn DB connection + * @return the lines + * @throws SQLException any sql errors + */ + List fetchAll(Connection conn) throws SQLException; + +} diff --git a/src/main/java/org/utplsql/api/outputBuffer/OutputBufferProvider.java b/src/main/java/org/utplsql/api/outputBuffer/OutputBufferProvider.java new file mode 100644 index 0000000..e29716e --- /dev/null +++ b/src/main/java/org/utplsql/api/outputBuffer/OutputBufferProvider.java @@ -0,0 +1,72 @@ +package org.utplsql.api.outputBuffer; + +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OracleTypes; +import org.utplsql.api.Version; +import org.utplsql.api.exception.InvalidVersionException; +import org.utplsql.api.reporter.Reporter; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.SQLException; + +public class OutputBufferProvider { + + private OutputBufferProvider() { + } + + /** + * Returns an OutputBuffer compatible with the given databaseVersion + * If we are at 3.1.0 or greater, returns an OutputBuffer based upon the information whether the Reporter has Output or not + * + * @param databaseVersion {@link Version} + * @param reporter {@link Reporter} + * @param conn {@link Connection} + * @return OutputBuffer + * @throws SQLException if there are problems with the database access + */ + public static OutputBuffer getCompatibleOutputBuffer(Version databaseVersion, Reporter reporter, Connection conn) throws SQLException { + OracleConnection oraConn = conn.unwrap(OracleConnection.class); + + try { + if (databaseVersion.isGreaterOrEqualThan(Version.V3_1_0)) { + if (hasOutput(reporter, oraConn)) { + return new DefaultOutputBuffer(reporter); + } else { + return new NonOutputBuffer(reporter); + } + } + } catch (InvalidVersionException ignored) { + } + + // If we couldn't find an appropriate OutputBuffer, return the Pre310-Compatibility-Buffer + return new CompatibilityOutputBufferPre310(reporter); + } + + private static boolean hasOutput(Reporter reporter, OracleConnection oraConn) throws SQLException { + + String sql = + "declare " + + " l_result int;" + + "begin " + + " begin " + + " execute immediate '" + + " begin " + + " :x := case ' || dbms_assert.simple_sql_name( ? ) || '() is of (ut_output_reporter_base) when true then 1 else 0 end;" + + " end;'" + + " using out l_result;" + + " end;" + + " ? := l_result;" + + "end;"; + + try (CallableStatement stmt = oraConn.prepareCall(sql)) { + stmt.setQueryTimeout(3); + stmt.setString(1, reporter.getTypeName()); + stmt.registerOutParameter(2, OracleTypes.INTEGER); + + stmt.execute(); + int result = stmt.getInt(2); + return result == 1; + } + } +} diff --git a/src/main/java/org/utplsql/api/package-info.java b/src/main/java/org/utplsql/api/package-info.java new file mode 100644 index 0000000..56a090f --- /dev/null +++ b/src/main/java/org/utplsql/api/package-info.java @@ -0,0 +1,4 @@ +@ParametersAreNonnullByDefault +package org.utplsql.api; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/org/utplsql/api/reporter/CoreReporters.java b/src/main/java/org/utplsql/api/reporter/CoreReporters.java new file mode 100644 index 0000000..a2b9f1f --- /dev/null +++ b/src/main/java/org/utplsql/api/reporter/CoreReporters.java @@ -0,0 +1,61 @@ +package org.utplsql.api.reporter; + +import org.utplsql.api.Version; +import org.utplsql.api.exception.InvalidVersionException; + +/** + * This enum defines default reporters, added and maintained by the utPLSQL team, + * and information since (and maybe until) which version they exist + * + * @author pesse + */ +public enum CoreReporters { + + UT_COVERAGE_COBERTURA_REPORTER(Version.V3_1_0, null), + UT_COVERAGE_HTML_REPORTER(Version.V3_0_0, null), + UT_COVERAGE_SONAR_REPORTER(Version.V3_0_0, null), + UT_COVERALLS_REPORTER(Version.V3_0_0, null), + UT_DEBUG_REPORTER(Version.V3_1_4, null), + UT_DOCUMENTATION_REPORTER(Version.V3_0_0, null), + UT_JUNIT_REPORTER(Version.V3_1_0, null), + UT_REALTIME_REPORTER(Version.V3_1_4, null), + UT_SONAR_TEST_REPORTER(Version.V3_0_0, null), + UT_TEAMCITY_REPORTER(Version.V3_0_0, null), + UT_TFS_JUNIT_REPORTER(Version.V3_1_0, null), + @Deprecated + UT_XUNIT_REPORTER(Version.V3_0_0, null); + + private final Version since; + private final Version until; + + CoreReporters(Version since, Version until) { + this.since = since; + this.until = until; + } + + public Version getSince() { + return since; + } + + public Version getUntil() { + return until; + } + + /** + * Checks whether a CoreReporter is valid for the given databaseVersion + * + * @param databaseVersion Database-Version + * @return true or false + */ + public boolean isAvailableFor(Version databaseVersion) { + try { + if ((since == null || databaseVersion.isGreaterOrEqualThan(since)) + && (until == null || databaseVersion.isLessOrEqualThan(until))) { + return true; + } + } catch (InvalidVersionException ignored) { + } + + return false; + } +} diff --git a/src/main/java/org/utplsql/api/reporter/CoverageHTMLReporter.java b/src/main/java/org/utplsql/api/reporter/CoverageHTMLReporter.java index 790112c..2b1ebb4 100644 --- a/src/main/java/org/utplsql/api/reporter/CoverageHTMLReporter.java +++ b/src/main/java/org/utplsql/api/reporter/CoverageHTMLReporter.java @@ -1,12 +1,11 @@ package org.utplsql.api.reporter; -import org.utplsql.api.CustomTypes; +import org.utplsql.api.ResourceUtil; -import java.sql.SQLException; -import java.sql.SQLInput; -import java.sql.SQLOutput; +import java.nio.file.Path; +import java.nio.file.Paths; -public class CoverageHTMLReporter extends Reporter { +public class CoverageHTMLReporter extends DefaultReporter { // Could override Reporter.init and call ut_coverage_report_html_helper.get_default_html_assets_path from database, // but had permissions issues. @@ -16,17 +15,49 @@ public class CoverageHTMLReporter extends Reporter { private String assetsPath; public CoverageHTMLReporter() { - this(null, DEFAULT_ASSETS_PATH); + super(CoreReporters.UT_COVERAGE_HTML_REPORTER.name(), null); } - public CoverageHTMLReporter(String projectName, String assetsPath) { - this.projectName = projectName; - this.assetsPath = assetsPath; + public CoverageHTMLReporter(String selfType, Object[] attributes) { + super(selfType, attributes); + } + + /** + * Write the bundled assets necessary for the HTML Coverage report to a given targetPath + * + * @param targetDirectory Directory where the assets should be stored + */ + protected static void writeReportAssetsTo(Path targetDirectory) { + ResourceUtil.copyResources(Paths.get("CoverageHTMLReporter"), targetDirectory); } @Override - public String getSQLTypeName() throws SQLException { - return CustomTypes.UT_COVERAGE_HTML_REPORTER; + protected Object[] getAttributes() { + Object[] attributes = super.getAttributes(); + + attributes[3] = projectName; + attributes[4] = assetsPath; + + return attributes; + } + + @Override + protected void setAttributes(Object[] attributes) { + super.setAttributes(attributes); + + if (attributes != null) { + if (attributes[3] != null) { + projectName = String.valueOf(attributes[3]); + } else { + projectName = null; + } + + if (attributes[4] != null) { + assetsPath = String.valueOf(attributes[4]); + } else { + assetsPath = null; + } + } } public String getProjectName() { @@ -45,18 +76,4 @@ public void setAssetsPath(String assetsPath) { this.assetsPath = assetsPath; } - @Override - public void readSQL(SQLInput stream, String typeName) throws SQLException { - super.readSQL(stream, typeName); - setProjectName(stream.readString()); - setAssetsPath(stream.readString()); - } - - @Override - public void writeSQL(SQLOutput stream) throws SQLException { - super.writeSQL(stream); - stream.writeString(getProjectName()); - stream.writeString(getAssetsPath()); - } - } diff --git a/src/main/java/org/utplsql/api/reporter/CoverageSonarReporter.java b/src/main/java/org/utplsql/api/reporter/CoverageSonarReporter.java deleted file mode 100644 index cdd9546..0000000 --- a/src/main/java/org/utplsql/api/reporter/CoverageSonarReporter.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.utplsql.api.reporter; - -import org.utplsql.api.CustomTypes; - -import java.sql.SQLException; - -public class CoverageSonarReporter extends Reporter { - - @Override - public String getSQLTypeName() throws SQLException { - return CustomTypes.UT_COVERAGE_SONAR_REPORTER; - } - -} diff --git a/src/main/java/org/utplsql/api/reporter/CoverallsReporter.java b/src/main/java/org/utplsql/api/reporter/CoverallsReporter.java deleted file mode 100644 index 96dfceb..0000000 --- a/src/main/java/org/utplsql/api/reporter/CoverallsReporter.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.utplsql.api.reporter; - -import org.utplsql.api.CustomTypes; - -import java.sql.SQLException; - -public class CoverallsReporter extends Reporter { - - @Override - public String getSQLTypeName() throws SQLException { - return CustomTypes.UT_COVERALLS_REPORTER; - } - -} diff --git a/src/main/java/org/utplsql/api/reporter/DefaultReporter.java b/src/main/java/org/utplsql/api/reporter/DefaultReporter.java new file mode 100644 index 0000000..b478355 --- /dev/null +++ b/src/main/java/org/utplsql/api/reporter/DefaultReporter.java @@ -0,0 +1,23 @@ +package org.utplsql.api.reporter; + +import oracle.jdbc.OracleConnection; +import org.utplsql.api.compatibility.CompatibilityProxy; + +import java.sql.SQLException; + +/** + * This is a basic Reporter implementation, using ORAData interface + * + * @author pesse + */ +public class DefaultReporter extends Reporter { + + public DefaultReporter(String typeName, Object[] attributes) { + super(typeName, attributes); + } + + @Override + protected void initOutputBuffer(OracleConnection oraConn, CompatibilityProxy compatibilityProxy) throws SQLException { + outputBuffer = compatibilityProxy.getOutputBuffer(this, oraConn); + } +} diff --git a/src/main/java/org/utplsql/api/reporter/DefaultReporterFactoryMethodRegistrator.java b/src/main/java/org/utplsql/api/reporter/DefaultReporterFactoryMethodRegistrator.java new file mode 100644 index 0000000..f1880df --- /dev/null +++ b/src/main/java/org/utplsql/api/reporter/DefaultReporterFactoryMethodRegistrator.java @@ -0,0 +1,20 @@ +package org.utplsql.api.reporter; + +import org.utplsql.api.compatibility.CompatibilityProxy; + +/** + * Helper-class which registers default ReporterFactoryMethods based on the given databaseVersion + * + * @author pesse + */ +class DefaultReporterFactoryMethodRegistrator { + + public static void registerDefaultReporters(ReporterFactory reporterFactory, CompatibilityProxy compatibilityProxy) { + + // At the moment we don't have version-specific reporters which need a special MethodFactory + reporterFactory.registerReporterFactoryMethod(CoreReporters.UT_DOCUMENTATION_REPORTER.name(), DocumentationReporter::new, "Provides additional properties lvl and failed"); + reporterFactory.registerReporterFactoryMethod(CoreReporters.UT_COVERAGE_HTML_REPORTER.name(), CoverageHTMLReporter::new, "Provides additional properties projectName and assetPath"); + } + + +} diff --git a/src/main/java/org/utplsql/api/reporter/DocumentationReporter.java b/src/main/java/org/utplsql/api/reporter/DocumentationReporter.java index e3e88a4..6e36613 100644 --- a/src/main/java/org/utplsql/api/reporter/DocumentationReporter.java +++ b/src/main/java/org/utplsql/api/reporter/DocumentationReporter.java @@ -1,19 +1,36 @@ package org.utplsql.api.reporter; -import org.utplsql.api.CustomTypes; +import java.math.BigDecimal; -import java.sql.SQLException; -import java.sql.SQLInput; -import java.sql.SQLOutput; - -public class DocumentationReporter extends Reporter { +public class DocumentationReporter extends DefaultReporter { private int lvl; private int failed; public DocumentationReporter() { - this.lvl = 0; - this.failed = 0; + super(CoreReporters.UT_DOCUMENTATION_REPORTER.name(), null); + } + + public DocumentationReporter(String selfType, Object[] attributes) { + super(selfType, attributes); + } + + @Override + protected Object[] getAttributes() { + Object[] attributes = super.getAttributes(); + attributes[3] = lvl; + attributes[4] = failed; + return attributes; + } + + @Override + protected void setAttributes(Object[] attributes) { + super.setAttributes(attributes); + + if (attributes != null) { + lvl = ((BigDecimal) attributes[3]).intValue(); + failed = ((BigDecimal) attributes[4]).intValue(); + } } public int getLvl() { @@ -32,23 +49,4 @@ public void setFailed(int failed) { this.failed = failed; } - @Override - public String getSQLTypeName() throws SQLException { - return CustomTypes.UT_DOCUMENTATION_REPORTER; - } - - @Override - public void readSQL(SQLInput stream, String typeName) throws SQLException { - super.readSQL(stream, typeName); - setLvl(stream.readInt()); - setFailed(stream.readInt()); - } - - @Override - public void writeSQL(SQLOutput stream) throws SQLException { - super.writeSQL(stream); - stream.writeInt(getLvl()); - stream.writeInt(getFailed()); - } - } diff --git a/src/main/java/org/utplsql/api/reporter/Reporter.java b/src/main/java/org/utplsql/api/reporter/Reporter.java index 83d201f..6e91062 100644 --- a/src/main/java/org/utplsql/api/reporter/Reporter.java +++ b/src/main/java/org/utplsql/api/reporter/Reporter.java @@ -1,64 +1,129 @@ package org.utplsql.api.reporter; -import org.utplsql.api.DBHelper; +import oracle.jdbc.OracleCallableStatement; +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OracleTypes; +import oracle.sql.Datum; +import oracle.sql.ORAData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.outputBuffer.OutputBuffer; -import java.sql.*; -import java.util.Calendar; +import java.sql.Connection; +import java.sql.SQLException; /** - * Created by Vinicius on 13/04/2017. + * This is a basic Reporter implementation, using ORAData interface + * + * @author pesse */ -public abstract class Reporter implements SQLData { +public abstract class Reporter implements ORAData { + private static final Logger logger = LoggerFactory.getLogger(Reporter.class); + protected OutputBuffer outputBuffer; private String selfType; - private String reporterId; - private java.sql.Date startDate; + private String id; + private Object[] attributes; + private boolean init = false; - public Reporter() {} + public Reporter(String typeName, Object[] attributes) { + setTypeName(typeName); + setAttributes(attributes); + } + + public Reporter init(Connection con, CompatibilityProxy compatibilityProxy, ReporterFactory reporterFactory) throws SQLException { + + if (compatibilityProxy == null) { + compatibilityProxy = new CompatibilityProxy(con); + } + if (reporterFactory == null) { + reporterFactory = new ReporterFactory(); + } + + OracleConnection oraConn = con.unwrap(OracleConnection.class); + + initDbReporter(oraConn, reporterFactory); + + init = true; + + initOutputBuffer(oraConn, compatibilityProxy); - public Reporter init(Connection conn) throws SQLException { - setSelfType(getSQLTypeName()); - setStartDate(new java.sql.Date(Calendar.getInstance().getTimeInMillis())); - setReporterId(DBHelper.newSysGuid(conn)); return this; } - public String getSelfType() { - return this.selfType; + public Reporter init(Connection con) throws SQLException { + return init(con, null, null); + } + + protected abstract void initOutputBuffer(OracleConnection oraConn, CompatibilityProxy compatibilityProxy) throws SQLException; + + /** + * Initializes the Reporter from database + * This is necessary because we set up DefaultOutputBuffer (and maybe other stuff) we don't want to know and care about + * in the java API. Let's just do the instantiation of the Reporter in the database and map it into this object. + * + * @param oraConn {@link OracleConnection} + * @throws SQLException if there are problems with the database access + */ + private void initDbReporter(OracleConnection oraConn, ReporterFactory reporterFactory) throws SQLException { + OracleCallableStatement callableStatement = (OracleCallableStatement) oraConn.prepareCall("{? = call " + selfType + "()}"); + callableStatement.registerOutParameter(1, OracleTypes.STRUCT, "UT_REPORTER_BASE"); + callableStatement.execute(); + + Reporter obj = (Reporter) callableStatement.getORAData(1, reporterFactory); + + setAttributes(obj.getAttributes()); + + logger.debug("Database-reporter initialized, Type: {}, ID: {}", selfType, id); + } + + protected Object[] getAttributes() { + return attributes; } - private void setSelfType(String selfType) { - this.selfType = selfType; + protected void setAttributes(Object[] attributes) { + if (attributes != null) { + this.id = DatatypeConverter.printHexBinary((byte[]) attributes[1]); + } + this.attributes = attributes; } - public String getReporterId() { - return this.reporterId; + public boolean isInit() { + return init; } - private void setReporterId(String reporterId) { - this.reporterId = reporterId; + public String getTypeName() { + return this.selfType; } - public java.sql.Date getStartDate() { - return this.startDate; + protected void setTypeName(String typeName) { + this.selfType = typeName.replaceAll("[^0-9a-zA-Z_\\.]", ""); } - private void setStartDate(java.sql.Date startDate) { - this.startDate = startDate; + public String getId() { + return this.id; } - @Override - public void readSQL(SQLInput stream, String typeName) throws SQLException { - setSelfType(stream.readString()); - setReporterId(stream.readString()); - setStartDate(stream.readDate()); + + public Datum toDatum(Connection c) throws SQLException { + return (Datum) c.createStruct(getTypeName(), getAttributes()); } - @Override - public void writeSQL(SQLOutput stream) throws SQLException { - stream.writeString(getSelfType()); - stream.writeString(getReporterId()); - stream.writeDate(getStartDate()); + public OutputBuffer getOutputBuffer() { + return outputBuffer; } + private static class DatatypeConverter { + private static final char[] hexCode = "0123456789ABCDEF".toCharArray(); + + static String printHexBinary(byte[] data) { + StringBuilder r = new StringBuilder(data.length * 2); + for (byte b : data) { + r.append(hexCode[(b >> 4) & 0xF]); + r.append(hexCode[(b & 0xF)]); + } + return r.toString(); + } + } } diff --git a/src/main/java/org/utplsql/api/reporter/ReporterFactory.java b/src/main/java/org/utplsql/api/reporter/ReporterFactory.java index f76c6be..b569127 100644 --- a/src/main/java/org/utplsql/api/reporter/ReporterFactory.java +++ b/src/main/java/org/utplsql/api/reporter/ReporterFactory.java @@ -1,22 +1,165 @@ package org.utplsql.api.reporter; -import org.utplsql.api.CustomTypes; - -public final class ReporterFactory { - - private ReporterFactory() {} - - public static Reporter createReporter(String reporterName) { - switch (reporterName.toUpperCase()) { - case CustomTypes.UT_DOCUMENTATION_REPORTER: return new DocumentationReporter(); - case CustomTypes.UT_COVERAGE_HTML_REPORTER: return new CoverageHTMLReporter(); - case CustomTypes.UT_TEAMCITY_REPORTER: return new TeamCityReporter(); - case CustomTypes.UT_XUNIT_REPORTER: return new XUnitReporter(); - case CustomTypes.UT_COVERALLS_REPORTER: return new CoverallsReporter(); - case CustomTypes.UT_COVERAGE_SONAR_REPORTER: return new CoverageSonarReporter(); - case CustomTypes.UT_SONAR_TEST_REPORTER: return new SonarTestReporter(); - default: throw new RuntimeException("Reporter " + reporterName + " not implemented."); +import oracle.sql.Datum; +import oracle.sql.ORAData; +import oracle.sql.ORADataFactory; +import org.utplsql.api.compatibility.CompatibilityProxy; + +import javax.annotation.Nullable; +import java.sql.SQLException; +import java.sql.Struct; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; + +/** + * This class manages the instantiation of reporters. + * One can register a supplier method for a specific name which will then be callable via createReporter(name) + *

+ * Use the static createEmpty or createDefault methods to get a new instance. + * We don't allow direct instantiation because we want + *

    + *
  • Register default ReporterFactoryMethods for Core-Reporters
  • + *
  • Be able to add more than one ReporterFactory implementation due to backwards-compatibility in future
  • + *
+ * + * @author pesse + */ +public final class ReporterFactory implements ORADataFactory { + + private final Map reportFactoryMethodMap = new HashMap<>(); + + ReporterFactory() { + } + + /** + * Returns a new instance of an empty ReporterFactory with no registered ReporterFactoryMethods + * Normally, you should be using createDefault-method instead. + * + * @return a new ReporterFactory instance + */ + public static ReporterFactory createEmpty() { + return new ReporterFactory(); + } + + /** + * Returns a new instance of a ReporterFactory with the default ReporterFactoryMethods registered. + * This can depend on the version of utPLSQL, therefore you have to provide a CompatibilityProxy + * + * @param proxy Compatibility proxy + * @return a new ReporterFactory instance with all default ReporterFactoryMethods registered + */ + public static ReporterFactory createDefault(CompatibilityProxy proxy) { + ReporterFactory reporterFactory = new ReporterFactory(); + DefaultReporterFactoryMethodRegistrator.registerDefaultReporters(reporterFactory, proxy); + return reporterFactory; + } + + /** + * Registers a creation method for a specified reporter name. Overrides eventually existing creation method + * + * @param reporterName the reporter's name to register + * @param factoryMethod the method which will return the reporter + * @param description the description of the reporter + * @return Object with information about the registered reporter + */ + public synchronized ReporterFactoryMethodInfo registerReporterFactoryMethod(String reporterName, BiFunction factoryMethod, String description) { + return reportFactoryMethodMap.put(reporterName.toUpperCase(), new ReporterFactoryMethodInfo(factoryMethod, description)); + } + + /** + * Unregisters a specified reporter name. + * + * @param reporterName the reporter's name to unregister + * @return information about the reporter which was previously registered or null + */ + public synchronized ReporterFactoryMethodInfo unregisterReporterFactoryMethod(String reporterName) { + return reportFactoryMethodMap.remove(reporterName.toUpperCase()); + } + + /** + * Checks whether a given reporter has a registered FactoryMethod or not + * + * @param reporterName the reporter's name + * @return true or false + */ + public synchronized boolean hasRegisteredFactoryMethodFor(String reporterName) { + return reportFactoryMethodMap.containsKey(reporterName.toUpperCase()); + } + + /** + * Returns a new reporter of the given name. + * If no specific ReporterFactoryMethod is registered, returns a default {Reporter} + * + * @param reporterName the reporter's name to create a new instance of + * @param attributes attributes from STRUCT + * @return A reporter + */ + public Reporter createReporter(String reporterName, @Nullable Object[] attributes) { + + reporterName = reporterName.toUpperCase(); + BiFunction supplier = DefaultReporter::new; + + if (reportFactoryMethodMap.containsKey(reporterName)) { + + ReporterFactoryMethodInfo ri = reportFactoryMethodMap.get(reporterName); + if (ri == null) { + throw new RuntimeException("ReporterFactoryMethodInfo for " + reporterName + " was null"); + } + + supplier = ri.factoryMethod; + } + + if (supplier == null) { + throw new RuntimeException("No factory method for " + reporterName); + } + + return supplier.apply(reporterName, attributes); + } + + /** + * Returns a new reporter of the given name (or should do so). + * If no specific ReporterFactoryMethod is registered, returns a default {Reporter} + * + * @param reporterName Name of the reporter + * @return Reporter + */ + public Reporter createReporter(String reporterName) { + return createReporter(reporterName, null); + } + + /** + * Returns a set of all registered reporter's names + * + * @return Map of reporter names + */ + public Map getRegisteredReporterInfo() { + Map descMap = new HashMap<>(reportFactoryMethodMap.size()); + + for (Map.Entry entry : reportFactoryMethodMap.entrySet()) { + descMap.put(entry.getKey(), entry.getValue().description); } + return descMap; } + @Override + public ORAData create(Datum d, int sqlType) throws SQLException { + if (d == null) return null; + if (d instanceof Struct) { + String sqlName = ((Struct) d).getSQLTypeName(); + return createReporter(sqlName, ((Struct) d).getAttributes()); + } + + return null; + } + + public static class ReporterFactoryMethodInfo { + public final BiFunction factoryMethod; + public final String description; + + public ReporterFactoryMethodInfo(BiFunction factoryMethod, String description) { + this.factoryMethod = factoryMethod; + this.description = description; + } + } } diff --git a/src/main/java/org/utplsql/api/reporter/SonarTestReporter.java b/src/main/java/org/utplsql/api/reporter/SonarTestReporter.java deleted file mode 100644 index 66be22e..0000000 --- a/src/main/java/org/utplsql/api/reporter/SonarTestReporter.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.utplsql.api.reporter; - -import org.utplsql.api.CustomTypes; - -import java.sql.SQLException; - -public class SonarTestReporter extends Reporter { - - @Override - public String getSQLTypeName() throws SQLException { - return CustomTypes.UT_SONAR_TEST_REPORTER; - } - -} diff --git a/src/main/java/org/utplsql/api/reporter/TeamCityReporter.java b/src/main/java/org/utplsql/api/reporter/TeamCityReporter.java deleted file mode 100644 index 9c4499b..0000000 --- a/src/main/java/org/utplsql/api/reporter/TeamCityReporter.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.utplsql.api.reporter; - -import org.utplsql.api.CustomTypes; - -import java.sql.SQLException; - -public class TeamCityReporter extends Reporter { - - @Override - public String getSQLTypeName() throws SQLException { - return CustomTypes.UT_TEAMCITY_REPORTER; - } - -} diff --git a/src/main/java/org/utplsql/api/reporter/XUnitReporter.java b/src/main/java/org/utplsql/api/reporter/XUnitReporter.java deleted file mode 100644 index cf1a9e1..0000000 --- a/src/main/java/org/utplsql/api/reporter/XUnitReporter.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.utplsql.api.reporter; - -import org.utplsql.api.CustomTypes; - -import java.sql.SQLException; - -public class XUnitReporter extends Reporter { - - @Override - public String getSQLTypeName() throws SQLException { - return CustomTypes.UT_XUNIT_REPORTER; - } - -} diff --git a/src/main/java/org/utplsql/api/reporter/inspect/AbstractReporterInspector.java b/src/main/java/org/utplsql/api/reporter/inspect/AbstractReporterInspector.java new file mode 100644 index 0000000..bbab916 --- /dev/null +++ b/src/main/java/org/utplsql/api/reporter/inspect/AbstractReporterInspector.java @@ -0,0 +1,17 @@ +package org.utplsql.api.reporter.inspect; + +import org.utplsql.api.reporter.ReporterFactory; + +import java.sql.Connection; + +abstract class AbstractReporterInspector implements ReporterInspector { + + protected final ReporterFactory reporterFactory; + protected final Connection connection; + + AbstractReporterInspector(ReporterFactory reporterFactory, Connection conn) { + this.reporterFactory = reporterFactory; + this.connection = conn; + } + +} diff --git a/src/main/java/org/utplsql/api/reporter/inspect/ReporterInfo.java b/src/main/java/org/utplsql/api/reporter/inspect/ReporterInfo.java new file mode 100644 index 0000000..fb805d5 --- /dev/null +++ b/src/main/java/org/utplsql/api/reporter/inspect/ReporterInfo.java @@ -0,0 +1,35 @@ +package org.utplsql.api.reporter.inspect; + +/** + * Holds information about utPLSQL Reporter-Types + * + * @author pesse + */ +public class ReporterInfo { + + private final String name; + private final Type type; + private final String description; + + ReporterInfo(String name, Type type, String description) { + this.name = name; + this.type = type; + this.description = description; + } + + public String getName() { + return name; + } + + public Type getType() { + return type; + } + + public String getDescription() { + return description; + } + + public enum Type { + SQL, JAVA, SQL_WITH_JAVA + } +} diff --git a/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspector.java b/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspector.java new file mode 100644 index 0000000..878e3b0 --- /dev/null +++ b/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspector.java @@ -0,0 +1,48 @@ +package org.utplsql.api.reporter.inspect; + +import org.utplsql.api.Version; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.exception.InvalidVersionException; +import org.utplsql.api.reporter.ReporterFactory; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Gives information about available reporters + * + * @author pesse + */ +public interface ReporterInspector { + + /** + * Returns a new instance of a ReporterInspector, based on the utPLSQL version used in the connection + * + * @param reporterFactory {@link ReporterFactory} + * @param conn {@link Connection} + * @return ReporterInspector + * @throws SQLException if there are problems with the database access + * @throws InvalidVersionException if version is not valid + */ + static ReporterInspector create(ReporterFactory reporterFactory, Connection conn) throws SQLException, InvalidVersionException { + + CompatibilityProxy proxy = new CompatibilityProxy(conn); + + if (proxy.getUtPlsqlVersion().isGreaterOrEqualThan(Version.V3_1_0)) { + return new ReporterInspector310(reporterFactory, conn); + } else { + return new ReporterInspectorPre310(reporterFactory, conn); + } + } + + List getReporterInfos(); + + default Map getReporterInfoMap() { + return getReporterInfos().stream().collect(Collectors.toMap(ReporterInfo::getName, Function.identity())); + } + +} diff --git a/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspector310.java b/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspector310.java new file mode 100644 index 0000000..091e0f4 --- /dev/null +++ b/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspector310.java @@ -0,0 +1,75 @@ +package org.utplsql.api.reporter.inspect; + +import oracle.jdbc.OracleCallableStatement; +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OracleType; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.reporter.Reporter; +import org.utplsql.api.reporter.ReporterFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +/** + * ReporterInspector for v3.1.0 upwards + * + * @author pesse + */ +class ReporterInspector310 extends AbstractReporterInspector { + + private final Map registeredReporterFactoryMethods; + private final Set infos; + + ReporterInspector310(ReporterFactory reporterFactory, Connection conn) throws SQLException { + super(reporterFactory, conn); + registeredReporterFactoryMethods = reporterFactory.getRegisteredReporterInfo(); + + Set reporterInfos = new HashSet<>(); + try (PreparedStatement stmt = connection.prepareStatement("select * from table(ut_runner.get_reporters_list) order by 1")) { + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + reporterInfos.add(getReporterInfo(rs.getString(1))); + } + } + } + this.infos = reporterInfos; + + } + + @Override + public List getReporterInfos() { + return new ArrayList<>(infos); + } + + private ReporterInfo getReporterInfo(String reporterNameWithOwner) throws SQLException { + String reporterName = reporterNameWithOwner.substring(reporterNameWithOwner.indexOf(".") + 1).toUpperCase(); + + ReporterInfo.Type type = ReporterInfo.Type.SQL; + String description = getDescription(reporterName); + + if (registeredReporterFactoryMethods.containsKey(reporterName)) { + type = ReporterInfo.Type.SQL_WITH_JAVA; + description += "\n" + registeredReporterFactoryMethods.get(reporterName); + } + + return new ReporterInfo(reporterName, type, description); + } + + private String getDescription(String reporterName) throws SQLException { + CompatibilityProxy compatibilityProxy = new CompatibilityProxy(connection); + Reporter reporter = reporterFactory.createReporter(reporterName).init(connection, compatibilityProxy, reporterFactory); + OracleConnection oraCon = connection.unwrap(OracleConnection.class); + + try (OracleCallableStatement stmt = (OracleCallableStatement) oraCon.prepareCall("{ ? = call ?.get_description() }")) { + stmt.registerOutParameter(1, OracleType.VARCHAR2); + stmt.setORAData(2, reporter); + stmt.execute(); + + return stmt.getString(1); + } + } + +} diff --git a/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspectorPre310.java b/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspectorPre310.java new file mode 100644 index 0000000..40d85fd --- /dev/null +++ b/src/main/java/org/utplsql/api/reporter/inspect/ReporterInspectorPre310.java @@ -0,0 +1,58 @@ +package org.utplsql.api.reporter.inspect; + +import org.utplsql.api.Version; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.reporter.CoreReporters; +import org.utplsql.api.reporter.ReporterFactory; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; + +class ReporterInspectorPre310 extends AbstractReporterInspector { + + private final Map registeredReporterFactoryMethods; + private final Map descriptions = new HashMap<>(); + private final Set infos; + + ReporterInspectorPre310(ReporterFactory reporterFactory, Connection conn) throws SQLException { + super(reporterFactory, conn); + registeredReporterFactoryMethods = reporterFactory.getRegisteredReporterInfo(); + initDefaultDescriptions(); + + Version databaseVersion = new CompatibilityProxy(connection).getUtPlsqlVersion(); + this.infos = Arrays.stream(CoreReporters.values()) + .filter(r -> r.isAvailableFor(databaseVersion)) + .map(this::getReporterInfo) + .collect(Collectors.toSet()); + } + + private void initDefaultDescriptions() { + descriptions.put(CoreReporters.UT_COVERAGE_HTML_REPORTER, ""); + descriptions.put(CoreReporters.UT_COVERAGE_SONAR_REPORTER, ""); + descriptions.put(CoreReporters.UT_COVERALLS_REPORTER, ""); + descriptions.put(CoreReporters.UT_DOCUMENTATION_REPORTER, ""); + descriptions.put(CoreReporters.UT_SONAR_TEST_REPORTER, ""); + descriptions.put(CoreReporters.UT_TEAMCITY_REPORTER, ""); + descriptions.put(CoreReporters.UT_JUNIT_REPORTER, ""); + } + + @Override + public List getReporterInfos() { + return new ArrayList<>(this.infos); + } + + private ReporterInfo getReporterInfo(CoreReporters reporter) { + + ReporterInfo.Type type = ReporterInfo.Type.SQL; + String description = descriptions.get(reporter); + + if (registeredReporterFactoryMethods.containsKey(reporter.name())) { + type = ReporterInfo.Type.SQL_WITH_JAVA; + description += "\n" + registeredReporterFactoryMethods.get(reporter.name()); + } + + return new ReporterInfo(reporter.name(), type, description); + } +} diff --git a/src/main/java/org/utplsql/api/testRunner/AbstractTestRunnerStatement.java b/src/main/java/org/utplsql/api/testRunner/AbstractTestRunnerStatement.java deleted file mode 100644 index 201bc3e..0000000 --- a/src/main/java/org/utplsql/api/testRunner/AbstractTestRunnerStatement.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.utplsql.api.testRunner; - -import oracle.jdbc.OracleConnection; -import org.utplsql.api.*; -import org.utplsql.api.exception.SomeTestsFailedException; - -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Types; -import java.util.List; - -/** Abstract class which creates a callable statement for running tests - * The SQL to be used has to be implemented for there are differences between the Framework-versions - * - * @author pesse - */ -abstract class AbstractTestRunnerStatement implements TestRunnerStatement { - - protected TestRunnerOptions options; - protected Connection conn; - protected CallableStatement callableStatement; - - public AbstractTestRunnerStatement(TestRunnerOptions options, Connection conn) throws SQLException { - this.options = options; - this.conn = conn; - - createStatement(); - } - - protected abstract String getSql(); - - protected void createStatement() throws SQLException { - - OracleConnection oraConn = conn.unwrap(OracleConnection.class); - - callableStatement = conn.prepareCall(getSql()); - - int paramIdx = 0; - - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_VARCHAR2_LIST, options.pathList.toArray())); - - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_REPORTERS, options.reporterList.toArray())); - - if (options.coverageSchemes.isEmpty()) { - callableStatement.setNull(++paramIdx, Types.ARRAY, CustomTypes.UT_VARCHAR2_LIST); - } else { - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_VARCHAR2_LIST, options.coverageSchemes.toArray())); - } - - if (options.sourceMappingOptions == null) { - callableStatement.setNull(++paramIdx, Types.ARRAY, CustomTypes.UT_FILE_MAPPINGS); - } else { - List sourceMappings = FileMapper.buildFileMappingList(conn, options.sourceMappingOptions); - - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_FILE_MAPPINGS, sourceMappings.toArray())); - } - - if (options.testMappingOptions == null) { - callableStatement.setNull(++paramIdx, Types.ARRAY, CustomTypes.UT_FILE_MAPPINGS); - } else { - List sourceMappings = FileMapper.buildFileMappingList(conn, options.testMappingOptions); - - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_FILE_MAPPINGS, sourceMappings.toArray())); - } - - if (options.includeObjects.isEmpty()) { - callableStatement.setNull(++paramIdx, Types.ARRAY, CustomTypes.UT_VARCHAR2_LIST); - } else { - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_VARCHAR2_LIST, options.includeObjects.toArray())); - } - - if (options.excludeObjects.isEmpty()) { - callableStatement.setNull(++paramIdx, Types.ARRAY, CustomTypes.UT_VARCHAR2_LIST); - } else { - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_VARCHAR2_LIST, options.excludeObjects.toArray())); - } - } - - public void execute() throws SQLException { - callableStatement.execute(); - } - - public void close() throws SQLException { - if (callableStatement != null) - callableStatement.close(); - } -} diff --git a/src/main/java/org/utplsql/api/testRunner/ActualTestRunnerStatement.java b/src/main/java/org/utplsql/api/testRunner/ActualTestRunnerStatement.java deleted file mode 100644 index 321e12b..0000000 --- a/src/main/java/org/utplsql/api/testRunner/ActualTestRunnerStatement.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.utplsql.api.testRunner; - -import org.utplsql.api.TestRunnerOptions; - -import java.sql.Connection; -import java.sql.SQLException; - -/** Provides the call to run tests for the most actual Framework version. - * Includes fail on error - * - * @author pesse - */ -class ActualTestRunnerStatement extends AbstractTestRunnerStatement { - - public ActualTestRunnerStatement(TestRunnerOptions options, Connection connection ) throws SQLException { - super( options, connection); - } - - @Override - protected String getSql() { - // Workaround because Oracle JDBC doesn't support passing boolean to stored procedures. - String colorConsoleStr = Boolean.toString(options.colorConsole); - String failOnErrors = Boolean.toString(options.failOnErrors); - - return - "BEGIN " + - "ut_runner.run(" + - "a_paths => ?, " + - "a_reporters => ?, " + - "a_color_console => " + colorConsoleStr + ", " + - "a_coverage_schemes => ?, " + - "a_source_file_mappings => ?, " + - "a_test_file_mappings => ?, " + - "a_include_objects => ?, " + - "a_exclude_objects => ?, " + - "a_fail_on_errors => " + failOnErrors + "); " + - "END;"; - } -} diff --git a/src/main/java/org/utplsql/api/testRunner/DynamicTestRunnerStatement.java b/src/main/java/org/utplsql/api/testRunner/DynamicTestRunnerStatement.java new file mode 100644 index 0000000..71fe893 --- /dev/null +++ b/src/main/java/org/utplsql/api/testRunner/DynamicTestRunnerStatement.java @@ -0,0 +1,109 @@ +package org.utplsql.api.testRunner; + +import oracle.jdbc.OracleConnection; +import org.utplsql.api.CustomTypes; +import org.utplsql.api.TestRunnerOptions; +import org.utplsql.api.Version; +import org.utplsql.api.compatibility.OptionalFeatures; +import org.utplsql.api.db.DynamicParameterList; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.SQLException; + +public class DynamicTestRunnerStatement implements TestRunnerStatement { + + private CallableStatement stmt; + private final OracleConnection oracleConnection; + private final Version utPlSQlVersion; + private final TestRunnerOptions options; + private final DynamicParameterList dynamicParameterList; + + private DynamicTestRunnerStatement(Version utPlSQlVersion, OracleConnection connection, TestRunnerOptions options, CallableStatement statement) throws SQLException { + this.utPlSQlVersion = utPlSQlVersion; + this.oracleConnection = connection; + this.options = options; + this.stmt = statement; + + this.dynamicParameterList = initParameterList(); + + prepareStatement(); + } + + private DynamicParameterList initParameterList() throws SQLException { + + Object[] sourceMappings = (options.sourceMappingOptions != null) + ? FileMapper.buildFileMappingList(oracleConnection, options.sourceMappingOptions).toArray() + : null; + Object[] testMappings = (options.testMappingOptions != null) + ? FileMapper.buildFileMappingList(oracleConnection, options.testMappingOptions).toArray() + : null; + + DynamicParameterList.DynamicParameterListBuilder builder = DynamicParameterList.builder() + .addIfNotEmpty("a_paths", options.pathList.toArray(), CustomTypes.UT_VARCHAR2_LIST, oracleConnection) + .addIfNotEmpty("a_reporters", options.reporterList.toArray(), CustomTypes.UT_REPORTERS, oracleConnection) + .addIfNotEmpty("a_color_console", options.colorConsole) + .addIfNotEmpty("a_coverage_schemes", options.coverageSchemes.toArray(), CustomTypes.UT_VARCHAR2_LIST, oracleConnection) + .addIfNotEmpty("a_source_file_mappings", sourceMappings, CustomTypes.UT_FILE_MAPPINGS, oracleConnection) + .addIfNotEmpty("a_test_file_mappings", testMappings, CustomTypes.UT_FILE_MAPPINGS, oracleConnection) + .addIfNotEmpty("a_include_objects", options.includeObjects.toArray(), CustomTypes.UT_VARCHAR2_LIST, oracleConnection) + .addIfNotEmpty("a_exclude_objects", options.excludeObjects.toArray(), CustomTypes.UT_VARCHAR2_LIST, oracleConnection); + + if (OptionalFeatures.FAIL_ON_ERROR.isAvailableFor(utPlSQlVersion)) { + builder.addIfNotEmpty("a_fail_on_errors", options.failOnErrors); + } + if (OptionalFeatures.CLIENT_CHARACTER_SET.isAvailableFor(utPlSQlVersion)) { + builder.addIfNotEmpty("a_client_character_set", options.clientCharacterSet); + } + if (OptionalFeatures.RANDOM_EXECUTION_ORDER.isAvailableFor(utPlSQlVersion)) { + builder.addIfNotEmpty("a_random_test_order", options.randomTestOrder) + .addIfNotEmpty("a_random_test_order_seed", options.randomTestOrderSeed); + } + if (OptionalFeatures.TAGS.isAvailableFor(utPlSQlVersion)) { + builder.addIfNotEmpty("a_tags", options.getTagsAsString()); + } + if (OptionalFeatures.EXPR.isAvailableFor(utPlSQlVersion)) { + builder.addIfNotEmpty("a_include_schema_expr", options.includeSchemaExpr) + .addIfNotEmpty("a_include_object_expr", options.includeObjectExpr) + .addIfNotEmpty("a_exclude_schema_expr", options.excludeSchemaExpr) + .addIfNotEmpty("a_exclude_object_expr", options.excludeObjectExpr); + } + + return builder.build(); + } + + private void prepareStatement() throws SQLException { + if (stmt == null) { + String sql = "BEGIN " + + "ut_runner.run(" + + dynamicParameterList.getSql() + + ");" + + "END;"; + stmt = oracleConnection.prepareCall(sql); + } + + dynamicParameterList.setParamsStartWithIndex(stmt, 1); + } + + @Override + public void execute() throws SQLException { + stmt.execute(); + } + + @Override + public String getSql() { + return dynamicParameterList.getSql(); + } + + @Override + public void close() throws SQLException { + if (stmt != null) { + stmt.close(); + } + } + + public static DynamicTestRunnerStatement forVersion(Version version, Connection connection, TestRunnerOptions options, CallableStatement statement) throws SQLException { + OracleConnection oraConn = connection.unwrap(OracleConnection.class); + return new DynamicTestRunnerStatement(version, oraConn, options, statement); + } +} diff --git a/src/main/java/org/utplsql/api/testRunner/FileMapper.java b/src/main/java/org/utplsql/api/testRunner/FileMapper.java new file mode 100644 index 0000000..b599a5a --- /dev/null +++ b/src/main/java/org/utplsql/api/testRunner/FileMapper.java @@ -0,0 +1,84 @@ +package org.utplsql.api.testRunner; + + +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OracleTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.utplsql.api.CustomTypes; +import org.utplsql.api.FileMapperOptions; +import org.utplsql.api.FileMapping; +import org.utplsql.api.KeyValuePair; +import org.utplsql.api.db.DynamicParameterList; + +import java.sql.*; +import java.util.*; + +final class FileMapper { + + private static final Logger logger = LoggerFactory.getLogger(FileMapper.class); + + private FileMapper() { + } + + /** + * Call the database api to build the custom file mappings. + */ + private static Array buildFileMappingArray( + Connection conn, FileMapperOptions mapperOptions) throws SQLException { + OracleConnection oraConn = conn.unwrap(OracleConnection.class); + + Map> typeMap = conn.getTypeMap(); + typeMap.put(CustomTypes.UT_FILE_MAPPING, FileMapping.class); + typeMap.put(CustomTypes.UT_KEY_VALUE_PAIR, KeyValuePair.class); + conn.setTypeMap(typeMap); + + logger.debug("Building fileMappingArray"); + final Object[] filePathsArray = mapperOptions.getFilePaths().toArray(); + for ( Object elem : filePathsArray ) { + logger.debug("Path: " + elem); + } + Object[] typeMapArray = null; + if ( mapperOptions.getTypeMappings() != null ) { + typeMapArray = mapperOptions.getTypeMappings().toArray(); + } + + DynamicParameterList parameterList = DynamicParameterList.builder() + .add("a_file_paths", filePathsArray, CustomTypes.UT_VARCHAR2_LIST, oraConn) + .addIfNotEmpty("a_object_owner", mapperOptions.getObjectOwner()) + .addIfNotEmpty("a_file_to_object_type_mapping", typeMapArray, CustomTypes.UT_KEY_VALUE_PAIRS, oraConn) + .addIfNotEmpty("a_regex_pattern", mapperOptions.getRegexPattern()) + .addIfNotEmpty("a_object_owner_subexpression", mapperOptions.getOwnerSubExpression()) + .addIfNotEmpty("a_object_name_subexpression", mapperOptions.getNameSubExpression()) + .addIfNotEmpty("a_object_type_subexpression", mapperOptions.getTypeSubExpression()) + .build(); + + CallableStatement callableStatement = conn.prepareCall( + "BEGIN " + + "? := ut_file_mapper.build_file_mappings(" + + parameterList.getSql() + + "); " + + "END;"); + + int paramIdx = 0; + callableStatement.registerOutParameter(++paramIdx, OracleTypes.ARRAY, CustomTypes.UT_FILE_MAPPINGS); + + parameterList.setParamsStartWithIndex(callableStatement, ++paramIdx); + + callableStatement.execute(); + return callableStatement.getArray(1); + } + + static List buildFileMappingList( + Connection conn, FileMapperOptions mapperOptions) throws SQLException { + java.sql.Array fileMappings = buildFileMappingArray(conn, mapperOptions); + + List mappingList = new ArrayList<>(); + for (Object obj : (Object[]) fileMappings.getArray()) { + mappingList.add((FileMapping) obj); + } + + return mappingList; + } + +} diff --git a/src/main/java/org/utplsql/api/testRunner/Pre303TestRunnerStatement.java b/src/main/java/org/utplsql/api/testRunner/Pre303TestRunnerStatement.java deleted file mode 100644 index b741459..0000000 --- a/src/main/java/org/utplsql/api/testRunner/Pre303TestRunnerStatement.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.utplsql.api.testRunner; - -import org.utplsql.api.TestRunnerOptions; - -import java.sql.Connection; -import java.sql.SQLException; - -/** TestRunner-Statement for Framework version before 3.0.3 - * Does not know about failOnErrors option - * - * @author pesse - */ -class Pre303TestRunnerStatement extends AbstractTestRunnerStatement { - - public Pre303TestRunnerStatement(TestRunnerOptions options, Connection conn) throws SQLException { - super(options, conn); - } - - @Override - protected String getSql() { - // Workaround because Oracle JDBC doesn't support passing boolean to stored procedures. - String colorConsoleStr = Boolean.toString(options.colorConsole); - - return "BEGIN " + - "ut_runner.run(" + - "a_paths => ?, " + - "a_reporters => ?, " + - "a_color_console => " + colorConsoleStr + ", " + - "a_coverage_schemes => ?, " + - "a_source_file_mappings => ?, " + - "a_test_file_mappings => ?, " + - "a_include_objects => ?, " + - "a_exclude_objects => ?); " + - "END;"; - } -} diff --git a/src/main/java/org/utplsql/api/testRunner/TestRunnerStatement.java b/src/main/java/org/utplsql/api/testRunner/TestRunnerStatement.java index ab3fa6a..dbc5b71 100644 --- a/src/main/java/org/utplsql/api/testRunner/TestRunnerStatement.java +++ b/src/main/java/org/utplsql/api/testRunner/TestRunnerStatement.java @@ -2,13 +2,17 @@ import java.sql.SQLException; -/** Interface to hide the concrete Statement-implementations of TestRunner +/** + * Interface to hide the concrete Statement-implementations of TestRunner * * @author pesse */ -public interface TestRunnerStatement { +public interface TestRunnerStatement extends AutoCloseable { void execute() throws SQLException; + String getSql(); + + @Override void close() throws SQLException; } diff --git a/src/main/java/org/utplsql/api/testRunner/TestRunnerStatementProvider.java b/src/main/java/org/utplsql/api/testRunner/TestRunnerStatementProvider.java index bbe6450..f263237 100644 --- a/src/main/java/org/utplsql/api/testRunner/TestRunnerStatementProvider.java +++ b/src/main/java/org/utplsql/api/testRunner/TestRunnerStatementProvider.java @@ -1,40 +1,32 @@ package org.utplsql.api.testRunner; -import org.utplsql.api.DBHelper; import org.utplsql.api.TestRunnerOptions; import org.utplsql.api.Version; -import org.utplsql.api.compatibility.OptionalFeatures; -import org.utplsql.api.exception.InvalidVersionException; import java.sql.Connection; import java.sql.SQLException; -/** Provides different implementations of TestRunnerStatement based on the version of the database framework +/** + * Provides different implementations of TestRunnerStatement based on the version of the database framework * * @author pesse */ public class TestRunnerStatementProvider { - /** Returns the TestRunnerStatement-implementation compatible with the given databaseVersion. + private TestRunnerStatementProvider() { + throw new UnsupportedOperationException(); + } + + /** + * Returns the TestRunnerStatement-implementation compatible with the given databaseVersion. * * @param databaseVersion Version of the database framework - * @param options TestRunnerOptions to be used - * @param conn Active Connection + * @param options TestRunnerOptions to be used + * @param conn Active Connection * @return TestRunnerStatment compatible with the database framework - * @throws SQLException + * @throws SQLException if there are problems with the database access */ - public static TestRunnerStatement getCompatibleTestRunnerStatement(Version databaseVersion, TestRunnerOptions options, Connection conn ) throws SQLException { - AbstractTestRunnerStatement stmt = null; - - try { - if (databaseVersion.isLessThan(new Version("3.0.3"))) - stmt = new Pre303TestRunnerStatement(options, conn); - - } catch ( InvalidVersionException e ) {} - - if ( stmt == null ) - stmt = new ActualTestRunnerStatement(options, conn); - - return stmt; + public static TestRunnerStatement getCompatibleTestRunnerStatement(Version databaseVersion, TestRunnerOptions options, Connection conn) throws SQLException { + return DynamicTestRunnerStatement.forVersion(databaseVersion, conn, options, null); } } diff --git a/src/main/resources/utplsql-api.version b/src/main/resources/utplsql-api.version new file mode 100644 index 0000000..ad96e7c --- /dev/null +++ b/src/main/resources/utplsql-api.version @@ -0,0 +1 @@ +${project.version} diff --git a/src/test/java/org/utplsql/api/AbstractDatabaseTest.java b/src/test/java/org/utplsql/api/AbstractDatabaseTest.java new file mode 100644 index 0000000..31ac56c --- /dev/null +++ b/src/test/java/org/utplsql/api/AbstractDatabaseTest.java @@ -0,0 +1,55 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractDatabaseTest { + + private static final String DB_URL; + private static final String DB_USER; + private static final String DB_PASS; + + static { + DB_URL = EnvironmentVariableUtil.getEnvValue("DB_URL", "localhost:1521:XE"); + DB_USER = EnvironmentVariableUtil.getEnvValue("DB_USER", "app"); + DB_PASS = EnvironmentVariableUtil.getEnvValue("DB_PASS", "pass"); + } + + private Connection conn; + private final List connectionList = new ArrayList<>(); + + public static String getUser() { + return DB_USER; + } + + @BeforeEach + public void setupConnection() throws SQLException { + conn = newConnection(); + } + + protected Connection getConnection() { + return conn; + } + + protected synchronized Connection newConnection() throws SQLException { + Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@" + DB_URL, DB_USER, DB_PASS); + connectionList.add(conn); + return conn; + } + + @AfterEach + public void teardownConnection() { + for (Connection conn : connectionList) { + try { + conn.close(); + } catch (SQLException ignored) { + } + } + } +} diff --git a/src/test/java/org/utplsql/api/CompatibilityIT.java b/src/test/java/org/utplsql/api/CompatibilityIT.java new file mode 100644 index 0000000..948948a --- /dev/null +++ b/src/test/java/org/utplsql/api/CompatibilityIT.java @@ -0,0 +1,26 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; +import org.utplsql.api.compatibility.CompatibilityProxy; + +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CompatibilityIT extends AbstractDatabaseTest { + + + @Test + void compatibleVersion() throws SQLException { + CompatibilityProxy proxy = new CompatibilityProxy(getConnection()); + proxy.failOnNotCompatible(); + assertTrue(proxy.isCompatible()); + } + + @Test + void skipCompatibilityCheck() throws SQLException { + CompatibilityProxy proxy = new CompatibilityProxy(getConnection(), Version.LATEST); + proxy.failOnNotCompatible(); + assertTrue(proxy.isCompatible()); + } +} diff --git a/src/test/java/org/utplsql/api/CompatibilityTest.java b/src/test/java/org/utplsql/api/CompatibilityTest.java deleted file mode 100644 index acee555..0000000 --- a/src/test/java/org/utplsql/api/CompatibilityTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.utplsql.api; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.utplsql.api.compatibility.CompatibilityProxy; -import org.utplsql.api.rules.DatabaseRule; - -import java.sql.Connection; -import java.sql.SQLException; - -public class CompatibilityTest { - - @Rule - public final DatabaseRule db = new DatabaseRule(); - - @Test - public void compatibleVersion() { - try { - Connection conn = db.newConnection(); - CompatibilityProxy proxy = new CompatibilityProxy(conn); - proxy.failOnNotCompatible(); - Assert.assertEquals(true, proxy.isCompatible()); - } catch (SQLException e) { - e.printStackTrace(); - Assert.fail(); - } - } - - @Test - public void skipCompatibilityCheck() - { - try { - Connection conn = db.newConnection(); - CompatibilityProxy proxy = new CompatibilityProxy(conn, true); - proxy.failOnNotCompatible(); - Assert.assertEquals(true, proxy.isCompatible()); - Assert.assertEquals(CompatibilityProxy.UTPLSQL_API_VERSION, proxy.getDatabaseVersion().toString()); - } catch (SQLException e) { - e.printStackTrace(); - Assert.fail(); - } - } -} diff --git a/src/test/java/org/utplsql/api/DBHelperIT.java b/src/test/java/org/utplsql/api/DBHelperIT.java new file mode 100644 index 0000000..41f85c4 --- /dev/null +++ b/src/test/java/org/utplsql/api/DBHelperIT.java @@ -0,0 +1,25 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DBHelperIT extends AbstractDatabaseTest { + + @Test + void getFrameworkVersion() throws SQLException { + Version v = DBHelper.getDatabaseFrameworkVersion(getConnection()); + assertTrue(v.isValid()); + System.out.println(v.getNormalizedString() + " - " + v); + } + + @Test + void getOracleDatabaseVersion() throws SQLException { + String databaseVersion = DBHelper.getOracleDatabaseVersion(getConnection()); + assertNotNull(databaseVersion); + } + +} diff --git a/src/test/java/org/utplsql/api/DBHelperTest.java b/src/test/java/org/utplsql/api/DBHelperTest.java deleted file mode 100644 index f017af2..0000000 --- a/src/test/java/org/utplsql/api/DBHelperTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.utplsql.api; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.utplsql.api.rules.DatabaseRule; - -import java.sql.SQLException; - -public class DBHelperTest { - - @Rule - public final DatabaseRule db = new DatabaseRule(); - - @Test - public void getFrameworkVersion() - { - try { - Version v = DBHelper.getDatabaseFrameworkVersion(db.newConnection()); - Assert.assertEquals(true, v.isValid()); - } catch (SQLException e) - { - e.printStackTrace(); - Assert.fail(); - } - } - -} diff --git a/src/test/java/org/utplsql/api/DatabaseInformationIT.java b/src/test/java/org/utplsql/api/DatabaseInformationIT.java new file mode 100644 index 0000000..2844625 --- /dev/null +++ b/src/test/java/org/utplsql/api/DatabaseInformationIT.java @@ -0,0 +1,30 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; +import org.utplsql.api.db.DatabaseInformation; +import org.utplsql.api.db.DefaultDatabaseInformation; + +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DatabaseInformationIT extends AbstractDatabaseTest { + + @Test + void getFrameworkVersion() throws SQLException { + DatabaseInformation databaseInformation = new DefaultDatabaseInformation(); + + Version v = databaseInformation.getUtPlsqlFrameworkVersion(getConnection()); + assertTrue(v.isValid()); + System.out.println(v.getNormalizedString() + " - " + v); + } + + @Test + void getOracleDatabaseVersion() throws SQLException { + DatabaseInformation databaseInformation = new DefaultDatabaseInformation(); + + String databaseVersion = databaseInformation.getOracleVersion(getConnection()); + assertNotNull(databaseVersion); + } +} diff --git a/src/test/java/org/utplsql/api/EnvironmentVariableUtil.java b/src/test/java/org/utplsql/api/EnvironmentVariableUtil.java new file mode 100644 index 0000000..90ca49f --- /dev/null +++ b/src/test/java/org/utplsql/api/EnvironmentVariableUtil.java @@ -0,0 +1,53 @@ +package org.utplsql.api; + +import javax.annotation.Nullable; + +/** + * This class provides an easy way to get environmental variables. + * This is mainly to improve testability but also to standardize the way how utPLSQL API and CLI read from + * environment. + *

+ * Variables are obtained from the following scopes in that order (chain breaks as soon as a value is obtained): + *

    + *
  • Properties (System.getProperty())
  • + *
  • Environment (System.getEnv())
  • + *
  • Default value
  • + *
+ *

+ * An empty string is treated the same as null. + * + * @author pesse + */ +public class EnvironmentVariableUtil { + + private EnvironmentVariableUtil() { + } + + /** + * Returns the value for a given key from environment (see class description) + * + * @param key Key of environment or property value + * @return Environment value or null + */ + public static String getEnvValue(String key) { + return getEnvValue(key, null); + } + + /** + * Returns the value for a given key from environment or a default value (see class description) + * + * @param key Key of environment or property value + * @param defaultValue Default value if nothing found + * @return Environment value or defaultValue + */ + public static String getEnvValue(String key, @Nullable String defaultValue) { + + String val = System.getProperty(key); + if (val == null || val.isEmpty()) val = System.getenv(key); + if (val == null || val.isEmpty()) val = defaultValue; + + return val; + } + + +} diff --git a/src/test/java/org/utplsql/api/EnvironmentVariableUtilTest.java b/src/test/java/org/utplsql/api/EnvironmentVariableUtilTest.java new file mode 100644 index 0000000..c827232 --- /dev/null +++ b/src/test/java/org/utplsql/api/EnvironmentVariableUtilTest.java @@ -0,0 +1,43 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + + +class EnvironmentVariableUtilTest { + + @Test + void testGetVariableFromEnvironment() { + // Let's find an environment variable which is not in Properties list and not empty + Set props = System.getProperties().keySet(); + Optional> envVariable = System.getenv().entrySet().stream() + .filter((e) -> !props.contains(e.getKey()) && e.getValue() != null && !e.getValue().isEmpty()) + .findFirst(); + + if (!envVariable.isPresent()) { + fail("Can't test for there is no environment variable not overridden by property"); + } + + assertEquals(envVariable.get().getValue(), EnvironmentVariableUtil.getEnvValue(envVariable.get().getKey())); + } + + @Test + void testGetVariableFromProperty() { + System.setProperty("PATH", "MyPath"); + + assertEquals("MyPath", EnvironmentVariableUtil.getEnvValue("PATH")); + } + + @Test + void testGetVariableFromDefault() { + + assertEquals("defaultValue", EnvironmentVariableUtil.getEnvValue("RANDOM" + System.currentTimeMillis(), "defaultValue")); + } + +} \ No newline at end of file diff --git a/src/test/java/org/utplsql/api/FileMapperTest.java b/src/test/java/org/utplsql/api/FileMapperTest.java deleted file mode 100644 index ed8e919..0000000 --- a/src/test/java/org/utplsql/api/FileMapperTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.utplsql.api; - -import org.utplsql.api.rules.DatabaseRule; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -public class FileMapperTest { - - @Rule - public final DatabaseRule db = new DatabaseRule(); - - @Test - public void testFileMapper() throws SQLException { - List typeMappings = new ArrayList<>(); - typeMappings.add(new KeyValuePair("procedures", "PROCEDURE")); - typeMappings.add(new KeyValuePair("functions", "FUNCTION")); - - List filePaths = java.util.Arrays.asList( - "sources/app/procedures/award_bonus.sql", - "sources/app/functions/betwnstr.sql"); - - FileMapperOptions mapperOptions = new FileMapperOptions(filePaths); - mapperOptions.setObjectOwner("APP"); - mapperOptions.setTypeMappings(typeMappings); - mapperOptions.setRegexPattern("\\w+[\\\\\\/](\\w+)[\\\\\\/](\\w+)[\\\\\\/](\\w+)[.](\\w{3})"); - mapperOptions.setOwnerSubExpression(1); - mapperOptions.setTypeSubExpression(2); - mapperOptions.setNameSubExpression(3); - - List fileMappings = FileMapper.buildFileMappingList(db.newConnection(), mapperOptions); - - if (fileMappings.size() != 2) - Assert.fail("Wrong mapping list size."); - - assertMapping(fileMappings.get(0), "APP", "AWARD_BONUS", "PROCEDURE"); - assertMapping(fileMappings.get(1), "APP", "BETWNSTR", "FUNCTION"); - } - - private void assertMapping(FileMapping fileMapping, String owner, String name, String type) { - Assert.assertEquals(owner, fileMapping.getObjectOwner()); - Assert.assertEquals(name, fileMapping.getObjectName()); - Assert.assertEquals(type, fileMapping.getObjectType()); - } - -} diff --git a/src/test/java/org/utplsql/api/JavaApiVersionTest.java b/src/test/java/org/utplsql/api/JavaApiVersionTest.java new file mode 100644 index 0000000..820b71d --- /dev/null +++ b/src/test/java/org/utplsql/api/JavaApiVersionTest.java @@ -0,0 +1,14 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.startsWith; + +class JavaApiVersionTest { + + @Test + void getJavaApiVersion() { + assertThat(JavaApiVersionInfo.getVersion(), startsWith("3.1")); + } +} diff --git a/src/test/java/org/utplsql/api/OptionalFeaturesIT.java b/src/test/java/org/utplsql/api/OptionalFeaturesIT.java new file mode 100644 index 0000000..f8fe9b3 --- /dev/null +++ b/src/test/java/org/utplsql/api/OptionalFeaturesIT.java @@ -0,0 +1,88 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.compatibility.OptionalFeatures; +import org.utplsql.api.exception.InvalidVersionException; + +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class OptionalFeaturesIT extends AbstractDatabaseTest { + + + private Version getDatabaseVersion() throws SQLException { + return new CompatibilityProxy(getConnection()).getUtPlsqlVersion(); + } + + @Test + void failOnError() throws SQLException, InvalidVersionException { + + boolean available = OptionalFeatures.FAIL_ON_ERROR.isAvailableFor(getConnection()); + + if (getDatabaseVersion().isGreaterOrEqualThan(Version.V3_0_3)) { + assertTrue(available); + } else { + assertFalse(available); + } + } + + @Test + void frameworkCompatibilityCheck() throws SQLException, InvalidVersionException { + + boolean available = OptionalFeatures.FRAMEWORK_COMPATIBILITY_CHECK.isAvailableFor(getConnection()); + + if (getDatabaseVersion().isGreaterOrEqualThan(Version.V3_0_3)) { + assertTrue(available); + } else { + assertFalse(available); + } + } + + @Test + void customReporters() throws SQLException, InvalidVersionException { + + boolean available = OptionalFeatures.CUSTOM_REPORTERS.isAvailableFor(getConnection()); + + if (getDatabaseVersion().isGreaterOrEqualThan(Version.V3_1_0)) { + assertTrue(available); + } else { + assertFalse(available); + } + } + + @Test + void clientCharset() throws SQLException, InvalidVersionException { + boolean available = OptionalFeatures.CLIENT_CHARACTER_SET.isAvailableFor(getConnection()); + + if (getDatabaseVersion().isGreaterOrEqualThan(Version.V3_1_2)) { + assertTrue(available); + } else { + assertFalse(available); + } + } + + @Test + void randomExecutionOrder() throws SQLException, InvalidVersionException { + boolean available = OptionalFeatures.RANDOM_EXECUTION_ORDER.isAvailableFor(getConnection()); + + if (getDatabaseVersion().isGreaterOrEqualThan(Version.V3_1_7)) { + assertTrue(available); + } else { + assertFalse(available); + } + } + + @Test + void tags() throws SQLException, InvalidVersionException { + boolean available = OptionalFeatures.TAGS.isAvailableFor(getConnection()); + + if (getDatabaseVersion().isGreaterOrEqualThan(Version.V3_1_7)) { + assertTrue(available); + } else { + assertFalse(available); + } + } +} diff --git a/src/test/java/org/utplsql/api/OutputBufferIT.java b/src/test/java/org/utplsql/api/OutputBufferIT.java new file mode 100644 index 0000000..6dd2742 --- /dev/null +++ b/src/test/java/org/utplsql/api/OutputBufferIT.java @@ -0,0 +1,151 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.exception.InvalidVersionException; +import org.utplsql.api.reporter.CoreReporters; +import org.utplsql.api.reporter.DefaultReporter; +import org.utplsql.api.reporter.DocumentationReporter; +import org.utplsql.api.reporter.Reporter; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyIterable; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Integration-test for OutputBuffers + * + * @author viniciusam + * @author pesse + */ +class OutputBufferIT extends AbstractDatabaseTest { + + private Reporter createReporter() throws SQLException { + Reporter reporter = new DocumentationReporter().init(newConnection()); + System.out.println("Reporter ID: " + reporter.getId()); + return reporter; + } + + @Test + void printAvailableLines() throws SQLException { + ExecutorService executorService = Executors.newFixedThreadPool(2); + try { + final Reporter reporter = createReporter(); + + Future task1 = executorService.submit(() -> { + try { + new TestRunner() + .addPath(getUser()) + .addReporter(reporter) + .run(getConnection()); + + return Boolean.TRUE; + } catch (SQLException e) { + return e; + } + }); + + Future task2 = executorService.submit(() -> { + FileOutputStream fileOutStream = null; + File outFile = new File("output.txt"); + try { + fileOutStream = new FileOutputStream(outFile); + + List printStreams = new ArrayList<>(); + printStreams.add(System.out); + printStreams.add(new PrintStream(fileOutStream)); + + reporter.getOutputBuffer() + .setFetchSize(1) + .printAvailable(newConnection(), printStreams); + + return Boolean.TRUE; + } catch (SQLException e) { + return e; + } finally { + if (fileOutStream != null) { + fileOutStream.close(); + outFile.delete(); + } + } + }); + + executorService.shutdown(); + executorService.awaitTermination(60, TimeUnit.MINUTES); + + Object res1 = task1.get(); + Object res2 = task2.get(); + + if (res1 instanceof Exception) { + fail((Exception) res1); + } + + if (res2 instanceof Exception) { + fail((Exception) res2); + } + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + @Test + void fetchAllLines() throws SQLException { + final Reporter reporter = createReporter(); + new TestRunner() + .addPath(getUser()) + .addReporter(reporter) + .run(getConnection()); + + List outputLines = reporter.getOutputBuffer().fetchAll(getConnection()); + + assertThat(outputLines, not(emptyIterable())); + } + + @Test + void getOutputFromSonarReporter() throws SQLException { + Reporter reporter = new DefaultReporter(CoreReporters.UT_SONAR_TEST_REPORTER.name(), null).init(newConnection()); + + new TestRunner() + .addPath(getUser()) + .addReporter(reporter) + .run(getConnection()); + + List outputLines = reporter.getOutputBuffer().fetchAll(getConnection()); + + assertThat(outputLines, not(emptyIterable())); + } + + @Test + void sonarReporterHasEncodingSet() throws SQLException, InvalidVersionException { + CompatibilityProxy proxy = new CompatibilityProxy(newConnection()); + + if (proxy.getUtPlsqlVersion().isGreaterOrEqualThan(Version.V3_1_2)) { + Reporter reporter = new DefaultReporter(CoreReporters.UT_SONAR_TEST_REPORTER.name(), null).init(getConnection()); + + TestRunner tr = new TestRunner() + .addPath(getUser()) + .addReporter(reporter); + + tr.run(getConnection()); + + List outputLines = reporter.getOutputBuffer().fetchAll(getConnection()); + + String defaultCharset = Charset.defaultCharset().toString(); + String actualCharset = outputLines.get(0).replaceAll("(.*)encoding=\"([^\"]+)\"(.*)", "$2"); + + assertEquals(defaultCharset.toLowerCase(), actualCharset.toLowerCase()); + } + + } +} diff --git a/src/test/java/org/utplsql/api/OutputBufferTest.java b/src/test/java/org/utplsql/api/OutputBufferTest.java deleted file mode 100644 index db043cb..0000000 --- a/src/test/java/org/utplsql/api/OutputBufferTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.utplsql.api; - -import org.utplsql.api.reporter.DocumentationReporter; -import org.utplsql.api.reporter.Reporter; -import org.utplsql.api.rules.DatabaseRule; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.PrintStream; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; - -/** - * Created by Vinicius on 13/04/2017. - */ -public class OutputBufferTest { - - @Rule - public final DatabaseRule db = new DatabaseRule(); - - public Reporter createReporter() throws SQLException { - Connection conn = db.newConnection(); - Reporter reporter = new DocumentationReporter().init(conn); - System.out.println("Reporter ID: " + reporter.getReporterId()); - return reporter; - } - - @Test - public void printAvailableLines() { - ExecutorService executorService = Executors.newFixedThreadPool(2); - - try { - final Reporter reporter = createReporter(); - - Future task1 = executorService.submit(() -> { - try { - Connection conn = db.newConnection(); - new TestRunner() - .addPath(db.getUser()) - .addReporter(reporter) - .run(conn); - - return Boolean.TRUE; - } catch (SQLException e) { - return e; - } - }); - - Future task2 = executorService.submit(() -> { - FileOutputStream fileOutStream = null; - File outFile = new File("output.txt"); - try { - Connection conn = db.newConnection(); - fileOutStream = new FileOutputStream(outFile); - - List printStreams = new ArrayList<>(); - printStreams.add(System.out); - printStreams.add(new PrintStream(fileOutStream)); - - new OutputBuffer(reporter) - .printAvailable(conn, printStreams); - - return Boolean.TRUE; - } catch (SQLException e) { - return e; - } finally { - if (fileOutStream != null) { - fileOutStream.close(); - outFile.delete(); - } - } - }); - - executorService.shutdown(); - executorService.awaitTermination(60, TimeUnit.MINUTES); - - Object res1 = task1.get(); - Object res2 = task2.get(); - - if (res1 instanceof Exception) - Assert.fail(((Exception) res1).getMessage()); - - if (res2 instanceof Exception) - Assert.fail(((Exception) res2).getMessage()); - } catch (SQLException e) { - Assert.fail(e.getMessage()); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - - @Test - public void fetchAllLines() { - try { - final Reporter reporter = createReporter(); - Connection conn = db.newConnection(); - new TestRunner() - .addPath(db.getUser()) - .addReporter(reporter) - .run(conn); - - List outputLines = new OutputBuffer(reporter) - .fetchAll(conn); - - Assert.assertTrue(outputLines.size() > 0); - } catch (SQLException e) { - Assert.fail(e.getMessage()); - } - } - -} diff --git a/src/test/java/org/utplsql/api/RegisterCustomReporterTest.java b/src/test/java/org/utplsql/api/RegisterCustomReporterTest.java new file mode 100644 index 0000000..71f6f63 --- /dev/null +++ b/src/test/java/org/utplsql/api/RegisterCustomReporterTest.java @@ -0,0 +1,33 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; +import org.utplsql.api.reporter.DefaultReporter; +import org.utplsql.api.reporter.Reporter; +import org.utplsql.api.reporter.ReporterFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class RegisterCustomReporterTest { + + @Test + void addCustomReporter() { + + ReporterFactory reporterFactory = ReporterFactory.createEmpty(); + reporterFactory.registerReporterFactoryMethod("ut_custom_reporter", + (type, attr) -> new DefaultReporter("UT_EXISTING_REPORTER", attr), + "My custom Reporter"); + + Reporter r = reporterFactory.createReporter("ut_custom_reporter"); + + assertEquals(r.getTypeName(), "UT_EXISTING_REPORTER"); + } + + @Test + void createCustomDefaultReporter() { + ReporterFactory reporterFactory = ReporterFactory.createEmpty(); + Reporter r = reporterFactory.createReporter("ut_custom_reporter"); + + assertEquals(r.getTypeName(), "UT_CUSTOM_REPORTER"); + } + +} diff --git a/src/test/java/org/utplsql/api/ReporterFactoryIT.java b/src/test/java/org/utplsql/api/ReporterFactoryIT.java new file mode 100644 index 0000000..512982c --- /dev/null +++ b/src/test/java/org/utplsql/api/ReporterFactoryIT.java @@ -0,0 +1,27 @@ +package org.utplsql.api; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.reporter.CoreReporters; +import org.utplsql.api.reporter.CoverageHTMLReporter; +import org.utplsql.api.reporter.DocumentationReporter; +import org.utplsql.api.reporter.ReporterFactory; + +import java.sql.SQLException; + +import static org.hamcrest.MatcherAssert.assertThat; + +class ReporterFactoryIT extends AbstractDatabaseTest { + + + @Test + void createDefaultReporterFactoryMethod() throws SQLException { + CompatibilityProxy proxy = new CompatibilityProxy(getConnection()); + + ReporterFactory reporterFactory = ReporterFactory.createDefault(proxy); + + assertThat(reporterFactory.createReporter(CoreReporters.UT_DOCUMENTATION_REPORTER.name()), Matchers.isA(DocumentationReporter.class)); + assertThat(reporterFactory.createReporter(CoreReporters.UT_COVERAGE_HTML_REPORTER.name()), Matchers.isA(CoverageHTMLReporter.class)); + } +} diff --git a/src/test/java/org/utplsql/api/ReporterInspectorIT.java b/src/test/java/org/utplsql/api/ReporterInspectorIT.java new file mode 100644 index 0000000..4f3f13d --- /dev/null +++ b/src/test/java/org/utplsql/api/ReporterInspectorIT.java @@ -0,0 +1,59 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Test; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.exception.InvalidVersionException; +import org.utplsql.api.reporter.CoreReporters; +import org.utplsql.api.reporter.ReporterFactory; +import org.utplsql.api.reporter.inspect.ReporterInfo; +import org.utplsql.api.reporter.inspect.ReporterInspector; + +import java.sql.SQLException; +import java.util.Comparator; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ReporterInspectorIT extends AbstractDatabaseTest { + + private ReporterFactory getReporterFactory() throws SQLException { + CompatibilityProxy proxy = new CompatibilityProxy(getConnection()); + + return ReporterFactory.createDefault(proxy); + } + + @Test + void testGetReporterInfo() throws SQLException, InvalidVersionException { + CompatibilityProxy proxy = new CompatibilityProxy(getConnection()); + + ReporterInspector inspector = ReporterInspector.create(getReporterFactory(), getConnection()); + + Map infos = inspector.getReporterInfoMap(); + + assertEquals(infos.get(CoreReporters.UT_COVERAGE_HTML_REPORTER.name()).getType(), ReporterInfo.Type.SQL_WITH_JAVA); + assertEquals(infos.get(CoreReporters.UT_COVERAGE_SONAR_REPORTER.name()).getType(), ReporterInfo.Type.SQL); + assertEquals(infos.get(CoreReporters.UT_COVERALLS_REPORTER.name()).getType(), ReporterInfo.Type.SQL); + assertEquals(infos.get(CoreReporters.UT_DOCUMENTATION_REPORTER.name()).getType(), ReporterInfo.Type.SQL_WITH_JAVA); + assertEquals(infos.get(CoreReporters.UT_SONAR_TEST_REPORTER.name()).getType(), ReporterInfo.Type.SQL); + assertEquals(infos.get(CoreReporters.UT_TEAMCITY_REPORTER.name()).getType(), ReporterInfo.Type.SQL); + assertEquals(infos.get(CoreReporters.UT_JUNIT_REPORTER.name()).getType(), ReporterInfo.Type.SQL); + + if (CoreReporters.UT_COVERAGE_COBERTURA_REPORTER.isAvailableFor(proxy.getUtPlsqlVersion())) { + assertEquals(infos.get(CoreReporters.UT_COVERAGE_COBERTURA_REPORTER.name()).getType(), ReporterInfo.Type.SQL); + } + } + + @Test + void printReporterInfos() throws SQLException, InvalidVersionException { + + ReporterInspector inspector = ReporterInspector.create(getReporterFactory(), getConnection()); + + inspector.getReporterInfos().stream() + .sorted(Comparator.comparing(ReporterInfo::getName)) + .forEach(r -> { + System.out.println(r.getName() + " (" + r.getType().name() + "): " + r.getDescription()); + System.out.println(); + }); + + } +} diff --git a/src/test/java/org/utplsql/api/ReporterNameTest.java b/src/test/java/org/utplsql/api/ReporterNameTest.java deleted file mode 100644 index cd7cc13..0000000 --- a/src/test/java/org/utplsql/api/ReporterNameTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.utplsql.api; - -import org.junit.Assert; -import org.junit.Test; -import org.utplsql.api.reporter.*; - -import java.sql.SQLException; - -public class ReporterNameTest { - - @Test - public void reporterSQLTypeName() throws SQLException { - Assert.assertEquals(CustomTypes.UT_COVERAGE_HTML_REPORTER, new CoverageHTMLReporter().getSQLTypeName()); - Assert.assertEquals(CustomTypes.UT_COVERAGE_SONAR_REPORTER, new CoverageSonarReporter().getSQLTypeName()); - Assert.assertEquals(CustomTypes.UT_COVERALLS_REPORTER, new CoverallsReporter().getSQLTypeName()); - Assert.assertEquals(CustomTypes.UT_DOCUMENTATION_REPORTER, new DocumentationReporter().getSQLTypeName()); - Assert.assertEquals(CustomTypes.UT_SONAR_TEST_REPORTER, new SonarTestReporter().getSQLTypeName()); - Assert.assertEquals(CustomTypes.UT_TEAMCITY_REPORTER, new TeamCityReporter().getSQLTypeName()); - Assert.assertEquals(CustomTypes.UT_XUNIT_REPORTER, new XUnitReporter().getSQLTypeName()); - } - - @Test - public void reporterFactory() { - Assert.assertTrue(ReporterFactory.createReporter(CustomTypes.UT_COVERAGE_HTML_REPORTER) - instanceof CoverageHTMLReporter); - Assert.assertTrue(ReporterFactory.createReporter(CustomTypes.UT_COVERAGE_SONAR_REPORTER) - instanceof CoverageSonarReporter); - Assert.assertTrue(ReporterFactory.createReporter(CustomTypes.UT_COVERALLS_REPORTER) - instanceof CoverallsReporter); - Assert.assertTrue(ReporterFactory.createReporter(CustomTypes.UT_DOCUMENTATION_REPORTER) - instanceof DocumentationReporter); - Assert.assertTrue(ReporterFactory.createReporter(CustomTypes.UT_SONAR_TEST_REPORTER) - instanceof SonarTestReporter); - Assert.assertTrue(ReporterFactory.createReporter(CustomTypes.UT_TEAMCITY_REPORTER) - instanceof TeamCityReporter); - Assert.assertTrue(ReporterFactory.createReporter(CustomTypes.UT_XUNIT_REPORTER) - instanceof XUnitReporter); - } - -} diff --git a/src/test/java/org/utplsql/api/TestRunnerIT.java b/src/test/java/org/utplsql/api/TestRunnerIT.java new file mode 100644 index 0000000..4ceb184 --- /dev/null +++ b/src/test/java/org/utplsql/api/TestRunnerIT.java @@ -0,0 +1,100 @@ +package org.utplsql.api; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.compatibility.OptionalFeatures; +import org.utplsql.api.db.DatabaseInformation; +import org.utplsql.api.db.DefaultDatabaseInformation; +import org.utplsql.api.exception.InvalidVersionException; +import org.utplsql.api.exception.SomeTestsFailedException; +import org.utplsql.api.reporter.CoreReporters; + +import java.sql.Connection; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Integration-test for TestRunner + * + * @author viniciusam + * @author pesse + */ +class TestRunnerIT extends AbstractDatabaseTest { + + @Test + void runWithDefaultParameters() throws SQLException { + new TestRunner().run(getConnection()); + } + + + /** + * This can only be run against versions >= 3.0.3 + */ + @Test + void runWithoutCompatibilityCheck() throws SQLException { + + DatabaseInformation databaseInformation = new DefaultDatabaseInformation(); + + // We can only test this for the versions of the latest TestRunnerStatement-Change + if ( OptionalFeatures.RANDOM_EXECUTION_ORDER.isAvailableFor(databaseInformation.getUtPlsqlFrameworkVersion(getConnection())) ) { + new TestRunner() + .skipCompatibilityCheck(true) + .run(getConnection()); + } + } + + @Test + void runWithManyReporters() throws SQLException { + Connection conn = getConnection(); + new TestRunner() + .addPath(getUser()) + .addReporter(CoreReporters.UT_DOCUMENTATION_REPORTER.name()) + .addReporter(CoreReporters.UT_COVERAGE_HTML_REPORTER.name()) + .addReporter(CoreReporters.UT_COVERAGE_SONAR_REPORTER.name()) + .addReporter(CoreReporters.UT_COVERALLS_REPORTER.name()) + .addReporter(CoreReporters.UT_SONAR_TEST_REPORTER.name()) + .addReporter(CoreReporters.UT_TEAMCITY_REPORTER.name()) + .addReporter(CoreReporters.UT_JUNIT_REPORTER.name()) + .run(conn); + } + + + /** + * This can only be tested on frameworks >= 3.0.3 + */ + @Disabled + @Test + void failOnErrors() throws SQLException, InvalidVersionException { + Connection conn = getConnection(); + + CompatibilityProxy proxy = new CompatibilityProxy(conn); + + if (proxy.getUtPlsqlVersion().isGreaterOrEqualThan(Version.V3_0_3)) { + Executable throwingTestRunner = () -> new TestRunner() + .failOnErrors(true) + .run(conn); + assertThrows(SomeTestsFailedException.class, throwingTestRunner); + } + } + + @Test + void runWithRandomExecutionOrder() throws SQLException { + CompatibilityProxy proxy = new CompatibilityProxy(getConnection()); + + new TestRunner() + .randomTestOrder(true) + .randomTestOrderSeed(123) + .run(getConnection()); + } + + @Test + void runWithTags() throws SQLException { + new TestRunner() + .addTag("none") + .run(getConnection()); + } + +} diff --git a/src/test/java/org/utplsql/api/TestRunnerTest.java b/src/test/java/org/utplsql/api/TestRunnerTest.java deleted file mode 100644 index b2076f1..0000000 --- a/src/test/java/org/utplsql/api/TestRunnerTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.utplsql.api; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.utplsql.api.compatibility.CompatibilityProxy; -import org.utplsql.api.exception.SomeTestsFailedException; -import org.utplsql.api.reporter.*; -import org.utplsql.api.rules.DatabaseRule; - -import java.sql.Connection; -import java.sql.SQLException; - -/** - * Created by Vinicius on 13/04/2017. - */ -public class TestRunnerTest { - - @Rule - public final DatabaseRule db = new DatabaseRule(); - - @Test - public void runWithDefaultParameters() { - try { - Connection conn = db.newConnection(); - new TestRunner().run(conn); - } catch (SQLException e) { - Assert.fail(e.getMessage()); - } - } - - @Test - /** This can only be run against versions >= 3.0.3 - */ - public void runWithoutCompatibilityCheck() { - try { - Connection conn = db.newConnection(); - CompatibilityProxy proxy = new CompatibilityProxy(conn); - - if ( proxy.getDatabaseVersion().isGreaterOrEqualThan(new Version("3.0.3"))) { - new TestRunner() - .skipCompatibilityCheck(true) - .run(conn); - } - } catch (Exception e) { - Assert.fail(e.getMessage()); - } - } - - @Test - public void runWithManyReporters() { - try { - Connection conn = db.newConnection(); - new TestRunner() - .addPath(db.getUser()) - .addReporter(new DocumentationReporter().init(conn)) - .addReporter(new CoverageHTMLReporter().init(conn)) - .addReporter(new CoverageSonarReporter().init(conn)) - .addReporter(new CoverallsReporter().init(conn)) - .addReporter(new SonarTestReporter().init(conn)) - .addReporter(new TeamCityReporter().init(conn)) - .addReporter(new XUnitReporter().init(conn)) - .run(conn); - } catch (SQLException e) { - Assert.fail(e.getMessage()); - } - } - - @Test - /** This can only be tested on frameworks >= 3.0.3 - */ - public void failOnErrors() { - try { - Connection conn = db.newConnection(); - - CompatibilityProxy proxy = new CompatibilityProxy(conn); - - if ( proxy.getDatabaseVersion().isGreaterOrEqualThan(new Version("3.0.3"))) { - new TestRunner() - .failOnErrors(true) - .run(conn); - Assert.fail(); - } - } catch (SomeTestsFailedException ignored) { - System.out.println("Expected exception object thrown."); - } catch (Exception e) { - Assert.fail("Wrong exception object thrown: " + e.getMessage()); - } - } - -} diff --git a/src/test/java/org/utplsql/api/VersionObjectTest.java b/src/test/java/org/utplsql/api/VersionObjectTest.java index 807e0cf..46cea83 100644 --- a/src/test/java/org/utplsql/api/VersionObjectTest.java +++ b/src/test/java/org/utplsql/api/VersionObjectTest.java @@ -1,123 +1,167 @@ package org.utplsql.api; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.utplsql.api.exception.InvalidVersionException; -public class VersionObjectTest { +import static org.junit.jupiter.api.Assertions.*; + +class VersionObjectTest { + + @Test + void versionPatternRecognitionFull() { + Version v = Version.create("v3.1.3.1234"); + + assertEquals(3, (long) v.getMajor()); + assertEquals(1, (long) v.getMinor()); + assertEquals(3, (long) v.getBugfix()); + assertEquals(1234, (long) v.getBuild()); + assertTrue(v.isValid()); + assertEquals("3.1.3.1234", v.getNormalizedString()); + } @Test - public void versionPatternRecognitionFull() { - Version v = new Version("v3.1.3.1234"); - - Assert.assertEquals(3, (long)v.getMajor()); - Assert.assertEquals(1, (long)v.getMinor()); - Assert.assertEquals(3, (long)v.getBugfix()); - Assert.assertEquals(1234, (long)v.getBuild()); - Assert.assertEquals(true, v.isValid()); - Assert.assertEquals("3.1.3.1234", v.getNormalizedString()); + void versionPatternRecognitionDevelop() { + Version v = Version.create("v3.1.3.2140-develop"); + + assertEquals(3, (long) v.getMajor()); + assertEquals(1, (long) v.getMinor()); + assertEquals(3, (long) v.getBugfix()); + assertEquals(2140, (long) v.getBuild()); + assertTrue(v.isValid()); + assertEquals("3.1.3.2140", v.getNormalizedString()); } @Test - public void versionPatternRecognitionPartial() { - Version v = new Version("3.1.etc"); - - Assert.assertEquals(3, (long)v.getMajor()); - Assert.assertEquals(1, (long)v.getMinor()); - Assert.assertNull(v.getBugfix()); - Assert.assertNull(v.getBuild()); - Assert.assertEquals(true, v.isValid()); - Assert.assertEquals("3.1", v.getNormalizedString()); + void versionPatternRecognitionPartial() { + Version v = Version.create("3.1.etc"); + + assertEquals(3, (long) v.getMajor()); + assertEquals(1, (long) v.getMinor()); + assertNull(v.getBugfix()); + assertNull(v.getBuild()); + assertTrue(v.isValid()); + assertEquals("3.1", v.getNormalizedString()); } @Test - public void versionPatternRecognitionInvalid() { - Version v = new Version("adseef"); - - Assert.assertNull(v.getMajor()); - Assert.assertNull(v.getMinor()); - Assert.assertNull(v.getBugfix()); - Assert.assertNull(v.getBuild()); - Assert.assertEquals(false, v.isValid()); - Assert.assertEquals("invalid", v.getNormalizedString()); + void versionPatternRecognitionInvalid() { + Version v = Version.create("adseef"); + + assertNull(v.getMajor()); + assertNull(v.getMinor()); + assertNull(v.getBugfix()); + assertNull(v.getBuild()); + assertFalse(v.isValid()); + assertEquals("invalid", v.getNormalizedString()); } @Test - public void versionCompareTo() - { - Version base = new Version("2.3.4.5"); + void versionCompareTo() { + Version base = Version.create("2.3.4.5"); // Less than - Assert.assertEquals(-1, base.compareTo(new Version("3"))); - Assert.assertEquals(-1, base.compareTo(new Version("3.2"))); - Assert.assertEquals(-1, base.compareTo(new Version("2.4.1"))); - Assert.assertEquals(-1, base.compareTo(new Version("2.3.9.1"))); - Assert.assertEquals(-1, base.compareTo(new Version("2.3.4.9"))); + assertEquals(-1, base.compareTo(Version.create("3"))); + assertEquals(-1, base.compareTo(Version.create("3.2"))); + assertEquals(-1, base.compareTo(Version.create("2.4.1"))); + assertEquals(-1, base.compareTo(Version.create("2.3.9.1"))); + assertEquals(-1, base.compareTo(Version.create("2.3.4.9"))); // Greater than - Assert.assertEquals(1, base.compareTo(new Version("1"))); - Assert.assertEquals(1, base.compareTo(new Version("1.6"))); - Assert.assertEquals(1, base.compareTo(new Version("2.2.4"))); - Assert.assertEquals(1, base.compareTo(new Version("2.3.3"))); - Assert.assertEquals(1, base.compareTo(new Version("2.3.4.1"))); - Assert.assertEquals(1, base.compareTo(new Version("2.3.4"))); + assertEquals(1, base.compareTo(Version.create("1"))); + assertEquals(1, base.compareTo(Version.create("1.6"))); + assertEquals(1, base.compareTo(Version.create("2.2.4"))); + assertEquals(1, base.compareTo(Version.create("2.3.3"))); + assertEquals(1, base.compareTo(Version.create("2.3.4.1"))); + assertEquals(1, base.compareTo(Version.create("2.3.4"))); // Equal - Assert.assertEquals(0, base.compareTo(new Version("2.3.4.5"))); + assertEquals(0, base.compareTo(Version.create("2.3.4.5"))); } @Test - public void isGreaterOrEqualThan() - { - Version base = new Version("2.3.4.5"); + void versionCompareToWithBaseNull() { + Version base = Version.create("2.3.4"); - try { - Assert.assertEquals(true, base.isGreaterOrEqualThan(new Version("1"))); - Assert.assertEquals(true, base.isGreaterOrEqualThan(new Version("2"))); - Assert.assertEquals(true, base.isGreaterOrEqualThan(new Version("2.3"))); - Assert.assertEquals(true, base.isGreaterOrEqualThan(new Version("2.2"))); - Assert.assertEquals(true, base.isGreaterOrEqualThan(new Version("2.3.4"))); - Assert.assertEquals(true, base.isGreaterOrEqualThan(new Version("2.3.3"))); - Assert.assertEquals(true, base.isGreaterOrEqualThan(new Version("2.3.4.5"))); - Assert.assertEquals(true, base.isGreaterOrEqualThan(new Version("2.3.4.4"))); - - Assert.assertEquals(false, base.isGreaterOrEqualThan(new Version("2.3.4.6"))); - Assert.assertEquals(false, base.isGreaterOrEqualThan(new Version("2.3.5"))); - Assert.assertEquals(false, base.isGreaterOrEqualThan(new Version("2.4"))); - Assert.assertEquals(false, base.isGreaterOrEqualThan(new Version("3"))); - } - catch ( InvalidVersionException e ) - { - e.printStackTrace(); - Assert.fail(); - } + // Less than + assertEquals(-1, base.compareTo(Version.create("3"))); + assertEquals(-1, base.compareTo(Version.create("3.2"))); + assertEquals(-1, base.compareTo(Version.create("2.4.1"))); + assertEquals(-1, base.compareTo(Version.create("2.3.9.1"))); + assertEquals(-1, base.compareTo(Version.create("2.3.4.1"))); + assertEquals(-1, base.compareTo(Version.create("2.3.4.5"))); + assertEquals(-1, base.compareTo(Version.create("2.3.4.9"))); + + // Greater than + assertEquals(1, base.compareTo(Version.create("1"))); + assertEquals(1, base.compareTo(Version.create("1.6"))); + assertEquals(1, base.compareTo(Version.create("2.2.4"))); + assertEquals(1, base.compareTo(Version.create("2.3.3"))); + + // Equal + assertEquals(0, base.compareTo(Version.create("2.3.4"))); } @Test - public void isGreaterOrEqualThanFails() - { + void isGreaterOrEqualThan() throws InvalidVersionException { + Version base = Version.create("2.3.4.5"); + + assertTrue(base.isGreaterOrEqualThan(Version.create("1"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.2"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.4"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.3"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.4.5"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.4.4"))); + + assertFalse(base.isGreaterOrEqualThan(Version.create("2.3.4.6"))); + assertFalse(base.isGreaterOrEqualThan(Version.create("2.3.5"))); + assertFalse(base.isGreaterOrEqualThan(Version.create("2.4"))); + assertFalse(base.isGreaterOrEqualThan(Version.create("3"))); + + } + + @Test + void isGreaterOrEqualThanWithBaseNull() throws InvalidVersionException { + Version base = Version.create("2.3.4"); + + assertTrue(base.isGreaterOrEqualThan(Version.create("1"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.2"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.4"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.3"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.4.5"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.4.4"))); + assertTrue(base.isGreaterOrEqualThan(Version.create("2.3.4.6"))); + + assertFalse(base.isGreaterOrEqualThan(Version.create("2.3.5"))); + assertFalse(base.isGreaterOrEqualThan(Version.create("2.4"))); + assertFalse(base.isGreaterOrEqualThan(Version.create("3"))); + + } + + @Test + void isGreaterOrEqualThanFails() { // Given version is invalid try { - new Version("2.3.4.5").isGreaterOrEqualThan(new Version("aerfvf")); - Assert.fail("Given Version is invalid - not recognized"); - } - catch ( InvalidVersionException e ) { + Version.create("2.3.4.5").isGreaterOrEqualThan(Version.create("aerfvf")); + fail("Given Version is invalid - not recognized"); + } catch (InvalidVersionException ignored) { } // Base version is invalid try { - new Version("erefs").isGreaterOrEqualThan(new Version("1.2.3")); - Assert.fail("Base version is invalid - not recognized"); - } - catch ( InvalidVersionException e ) { + Version.create("erefs").isGreaterOrEqualThan(Version.create("1.2.3")); + fail("Base version is invalid - not recognized"); + } catch (InvalidVersionException ignored) { } // Both versions are invalid try { - new Version("erefs").isGreaterOrEqualThan(new Version("aerfvf")); - Assert.fail("Both versions are invalid - not recognized"); - } - catch ( InvalidVersionException e ) { + Version.create("erefs").isGreaterOrEqualThan(Version.create("aerfvf")); + fail("Both versions are invalid - not recognized"); + } catch (InvalidVersionException ignored) { } } } diff --git a/src/test/java/org/utplsql/api/db/DynamicParameterListTest.java b/src/test/java/org/utplsql/api/db/DynamicParameterListTest.java new file mode 100644 index 0000000..e3e9a17 --- /dev/null +++ b/src/test/java/org/utplsql/api/db/DynamicParameterListTest.java @@ -0,0 +1,144 @@ +package org.utplsql.api.db; + +import oracle.jdbc.OracleConnection; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.sql.CallableStatement; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +public class DynamicParameterListTest { + + @Nested + class single_parameters { + @Test + void can_add_string() throws SQLException { + CallableStatement stmt = mock(CallableStatement.class); + + DynamicParameterList paramList = DynamicParameterList.builder() + .add("a_param", "MyString") + .build(); + + assertEquals("a_param => ?", paramList.getSql()); + + paramList.setParamsStartWithIndex(stmt, 5); + verify(stmt).setString(5, "MyString"); + } + + @Test + void can_add_int() throws SQLException { + CallableStatement stmt = mock(CallableStatement.class); + + DynamicParameterList paramList = DynamicParameterList.builder() + .add("a_param", 1234) + .build(); + + assertEquals("a_param => ?", paramList.getSql()); + + paramList.setParamsStartWithIndex(stmt, 10); + verify(stmt).setInt(10, 1234); + } + + @Test + void can_add_array() throws SQLException { + CallableStatement stmt = mock(CallableStatement.class); + OracleConnection conn = mock(OracleConnection.class); + + Object[] numArr = new Object[]{1, 2}; + + DynamicParameterList paramList = DynamicParameterList.builder() + .add("a_param", numArr, "MY_TYPE", conn) + .build(); + + assertEquals("a_param => ?", paramList.getSql()); + + paramList.setParamsStartWithIndex(stmt, 3); + verify(conn).createOracleArray("MY_TYPE", numArr); + verify(stmt).setArray(3, null); + } + + @Test + void can_add_boolean() throws SQLException { + CallableStatement stmt = mock(CallableStatement.class); + + DynamicParameterList paramList = DynamicParameterList.builder() + .add("a_bool", true) + .build(); + + assertEquals("a_bool => (case ? when 1 then true else false end)", paramList.getSql()); + + paramList.setParamsStartWithIndex(stmt, 3); + verify(stmt).setInt(3, 1); + } + } + + @Nested + class mutliple_parameters { + + @Test + void several_parameters_are_issued_in_the_correct_order() throws SQLException { + CallableStatement mockedStatement = mock(CallableStatement.class); + + DynamicParameterList parameterList = DynamicParameterList.builder() + .add("a_param1", "Param1") + .add("a_param2", "Param2") + .add("a_param3", "Param3") + .build(); + + assertEquals("a_param1 => ?, a_param2 => ?, a_param3 => ?", parameterList.getSql()); + + parameterList.setParamsStartWithIndex(mockedStatement, 10); + + verify(mockedStatement).setString(10, "Param1"); + verify(mockedStatement).setString(11, "Param2"); + verify(mockedStatement).setString(12, "Param3"); + } + + @Test + void call_with_three_different_types() throws SQLException { + + CallableStatement mockedStatement = mock(CallableStatement.class); + OracleConnection mockedConn = mock(OracleConnection.class); + + Object[] numArr = new Object[]{1, 2}; + + DynamicParameterList parameterList = DynamicParameterList.builder() + .add("a_object_owner", "MyOwner") + .add("a_num_param", 123) + .add("a_num_array", numArr, "MY_NUM_ARR", mockedConn) + .build(); + + assertEquals("a_object_owner => ?, a_num_param => ?, a_num_array => ?", parameterList.getSql()); + + parameterList.setParamsStartWithIndex(mockedStatement, 5); + + verify(mockedStatement).setString(5, "MyOwner"); + verify(mockedStatement).setInt(6, 123); + verify(mockedConn).createOracleArray("MY_NUM_ARR", numArr); + verify(mockedStatement).setArray(7, null); + } + + @Test + void when_not_accept_empty_filter_empty_elements() throws SQLException { + + CallableStatement mockedStatement = mock(CallableStatement.class); + OracleConnection mockedConn = mock(OracleConnection.class); + + DynamicParameterList parameterList = DynamicParameterList.builder() + .addIfNotEmpty("a_object_owner", (String) null) + .addIfNotEmpty("a_num_param", (Integer) null) + .addIfNotEmpty("a_num_array", new Object[]{}, "MY_NUM_ARR", mockedConn) + .build(); + + assertEquals("", parameterList.getSql()); + + parameterList.setParamsStartWithIndex(mockedStatement, 2); + + verifyNoMoreInteractions(mockedStatement); + verifyNoMoreInteractions(mockedConn); + } + } +} diff --git a/src/test/java/org/utplsql/api/outputBuffer/OutputBufferProviderIT.java b/src/test/java/org/utplsql/api/outputBuffer/OutputBufferProviderIT.java new file mode 100644 index 0000000..463e0be --- /dev/null +++ b/src/test/java/org/utplsql/api/outputBuffer/OutputBufferProviderIT.java @@ -0,0 +1,49 @@ +package org.utplsql.api.outputBuffer; + +import org.junit.jupiter.api.Test; +import org.utplsql.api.AbstractDatabaseTest; +import org.utplsql.api.Version; +import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.exception.InvalidVersionException; +import org.utplsql.api.reporter.CoreReporters; +import org.utplsql.api.reporter.Reporter; +import org.utplsql.api.reporter.ReporterFactory; + +import java.sql.SQLException; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsInstanceOf.instanceOf; + +public class OutputBufferProviderIT extends AbstractDatabaseTest { + + @Test + void testGettingPre310Version() throws SQLException { + + CompatibilityProxy proxy = new CompatibilityProxy(getConnection(), Version.V3_0_4); + ReporterFactory reporterFactory = ReporterFactory.createDefault(proxy); + + Reporter r = reporterFactory.createReporter(CoreReporters.UT_DOCUMENTATION_REPORTER.name()); + r.init(getConnection(), proxy, reporterFactory); + + OutputBuffer buffer = proxy.getOutputBuffer(r, getConnection()); + + assertThat(buffer, instanceOf(CompatibilityOutputBufferPre310.class)); + } + + @Test + void testGettingActualVersion() throws SQLException, InvalidVersionException { + CompatibilityProxy proxy = new CompatibilityProxy(getConnection(), Version.LATEST); + + // We can only test new behaviour with DB-Version >= 3.1.0 + if ( proxy.getRealDbPlsqlVersion().isGreaterOrEqualThan(Version.V3_1_0)) { + ReporterFactory reporterFactory = ReporterFactory.createDefault(proxy); + + Reporter r = reporterFactory.createReporter(CoreReporters.UT_DOCUMENTATION_REPORTER.name()); + r.init(getConnection(), proxy, reporterFactory); + + OutputBuffer buffer = proxy.getOutputBuffer(r, getConnection()); + + assertThat(buffer, instanceOf(DefaultOutputBuffer.class)); + } + } +} diff --git a/src/test/java/org/utplsql/api/reporter/CoverageHTMLReporterAssetTest.java b/src/test/java/org/utplsql/api/reporter/CoverageHTMLReporterAssetTest.java new file mode 100644 index 0000000..b94cc32 --- /dev/null +++ b/src/test/java/org/utplsql/api/reporter/CoverageHTMLReporterAssetTest.java @@ -0,0 +1,65 @@ +package org.utplsql.api.reporter; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("binary") +class CoverageHTMLReporterAssetTest { + + private static final String TEST_FOLDER = "__testAssets"; + + @TempDir + Path tempDir; + + private void testFileExists(Path filePath) { + File f = new File(tempDir.resolve(TEST_FOLDER).resolve(filePath).toUri()); + + assertTrue(f.exists(), () -> "File " + f + " does not exist"); + } + + @Disabled("No idea why this ever worked") + @Test + void writeReporterAssetsTo() throws RuntimeException { + + Path targetPath = tempDir.resolve(TEST_FOLDER); + + // Act + CoverageHTMLReporter.writeReportAssetsTo(targetPath); + + testFileExists(Paths.get("colorbox", "border.png")); + testFileExists(Paths.get("colorbox", "controls.png")); + testFileExists(Paths.get("colorbox", "loading.gif")); + testFileExists(Paths.get("colorbox", "loading_background.png")); + + testFileExists(Paths.get("images", "ui-bg_flat_0_aaaaaa_40x100.png")); + testFileExists(Paths.get("images", "ui-bg_flat_75_ffffff_40x100.png")); + testFileExists(Paths.get("images", "ui-bg_glass_55_fbf9ee_1x400.png")); + testFileExists(Paths.get("images", "ui-bg_glass_65_ffffff_1x400.png")); + testFileExists(Paths.get("images", "ui-bg_glass_75_dadada_1x400.png")); + testFileExists(Paths.get("images", "ui-bg_glass_75_e6e6e6_1x400.png")); + testFileExists(Paths.get("images", "ui-bg_glass_95_fef1ec_1x400.png")); + testFileExists(Paths.get("images", "ui-bg_highlight-soft_75_cccccc_1x100.png")); + testFileExists(Paths.get("images", "ui-icons_2e83ff_256x240.png")); + testFileExists(Paths.get("images", "ui-icons_222222_256x240.png")); + testFileExists(Paths.get("images", "ui-icons_454545_256x240.png")); + testFileExists(Paths.get("images", "ui-icons_888888_256x240.png")); + testFileExists(Paths.get("images", "ui-icons_cd0a0a_256x240.png")); + + testFileExists(Paths.get("application.css")); + testFileExists(Paths.get("application.js")); + testFileExists(Paths.get("favicon_green.png")); + testFileExists(Paths.get("favicon_red.png")); + testFileExists(Paths.get("favicon_yellow.png")); + testFileExists(Paths.get("loading.gif")); + testFileExists(Paths.get("magnify.png")); + + } +} diff --git a/src/test/java/org/utplsql/api/rules/DatabaseRule.java b/src/test/java/org/utplsql/api/rules/DatabaseRule.java deleted file mode 100644 index 79baf95..0000000 --- a/src/test/java/org/utplsql/api/rules/DatabaseRule.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.utplsql.api.rules; - -import org.junit.rules.ExternalResource; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -/** - * Created by Vinicius on 13/04/2017. - */ -public class DatabaseRule extends ExternalResource { - - private static String sUrl; - private static String sUser; - private static String sPass; - - static { - sUrl = System.getenv("DB_URL") != null ? System.getenv("DB_URL") : "192.168.99.100:1521:XE"; - sUser = System.getenv("DB_USER") != null ? System.getenv("DB_USER") : "app"; - sPass = System.getenv("DB_PASS") != null ? System.getenv("DB_PASS") : "app"; - } - - private List connectionList; - - public DatabaseRule() { - connectionList = new ArrayList<>(); - } - - public String getUser() { - return sUser; - } - - public synchronized Connection newConnection() throws SQLException { - Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@" + sUrl, sUser, sPass); - connectionList.add(conn); - return conn; - } - - @Override - protected void after() { - for (Connection conn : connectionList) - try { conn.close(); } catch (SQLException ignored) {} - } - -} diff --git a/src/test/java/org/utplsql/api/testRunner/DynamicTestRunnerStatementTest.java b/src/test/java/org/utplsql/api/testRunner/DynamicTestRunnerStatementTest.java new file mode 100644 index 0000000..739dbcf --- /dev/null +++ b/src/test/java/org/utplsql/api/testRunner/DynamicTestRunnerStatementTest.java @@ -0,0 +1,314 @@ +package org.utplsql.api.testRunner; + +import oracle.jdbc.OracleConnection; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.verification.VerificationMode; +import org.utplsql.api.CustomTypes; +import org.utplsql.api.FileMapping; +import org.utplsql.api.TestRunnerOptions; +import org.utplsql.api.Version; + +import java.sql.Array; +import java.sql.CallableStatement; +import java.sql.SQLException; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.*; + +public class DynamicTestRunnerStatementTest { + + private DynamicTestRunnerStatement testRunnerStatement; + private CallableStatement callableStatement; + private OracleConnection oracleConnection; + private TestRunnerOptions options; + private Object[] expectedFileMapping; + + @BeforeEach + void initParameters() throws SQLException { + expectedFileMapping = new Object[]{new FileMapping("someFile", "owner", "object", "PACKAGE")}; + + // Mock some internals. This is not pretty, but a first step + oracleConnection = getMockedOracleConnection(expectedFileMapping); + callableStatement = mock(CallableStatement.class); + + // Act + options = TestRunnerStatementProviderIT.getCompletelyFilledOptions(); + } + + @Test + void version_3_0_2_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_0_2); + + checkBaseParameters(); + checkFailOnError(false); + checkClientCharacterSet(false); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_0_3_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_0_3); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(false); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_0_4_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_0_4); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(false); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_1_0_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_0); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(false); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_1_1_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_1); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(false); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_1_2_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_2); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(true); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_1_3_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_3); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(true); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_1_4_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_4); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(true); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_1_5_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_5); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(true); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_1_6_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_6); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(true); + checkRandomTestOrder(false); + checkTags(false); + checkExpr(false); + } + + @Test + void version_3_1_7_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_7); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(true); + checkRandomTestOrder(true); + checkTags(true); + checkExpr(false); + } + + @Test + void version_3_1_8_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_8); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(true); + checkRandomTestOrder(true); + checkTags(true); + checkExpr(false); + } + + @Test + void version_3_1_13_parameters() throws SQLException { + initTestRunnerStatementForVersion(Version.V3_1_13); + + checkBaseParameters(); + checkFailOnError(true); + checkClientCharacterSet(true); + checkRandomTestOrder(true); + checkTags(true); + checkExpr(true); + } + + private OracleConnection getMockedOracleConnection(Object[] expectedFileMapping) throws SQLException { + OracleConnection oracleConnection = mock(OracleConnection.class); + when(oracleConnection.unwrap(OracleConnection.class)) + .thenReturn(oracleConnection); + mockFileMapper(oracleConnection, expectedFileMapping); + return oracleConnection; + } + + private void mockFileMapper(OracleConnection mockedOracleConnection, Object[] expectedFileMapping) throws SQLException { + Array fileMapperArray = mock(Array.class); + CallableStatement fileMapperStatement = mock(CallableStatement.class); + + when(fileMapperArray.getArray()) + .thenReturn(expectedFileMapping); + when(fileMapperStatement.getArray(1)) + .thenReturn(fileMapperArray); + when( + mockedOracleConnection.prepareCall(argThat( + a -> a.startsWith("BEGIN ? := ut_file_mapper.build_file_mappings(")) + )) + .thenReturn(fileMapperStatement); + } + + private Matcher doesOrDoesNotContainString(String string, boolean shouldBeThere) { + return (shouldBeThere) + ? containsString(string) + : not(containsString(string)); + } + + private VerificationMode doesOrDoesNotGetCalled(boolean shouldBeThere) { + return (shouldBeThere) + ? times(1) + : never(); + } + + private void initTestRunnerStatementForVersion(Version version) throws SQLException { + testRunnerStatement = DynamicTestRunnerStatement + .forVersion(version, oracleConnection, options, callableStatement); + } + + private void checkBaseParameters() throws SQLException { + String sql = testRunnerStatement.getSql(); + + assertThat(sql, containsString("a_paths => ?")); + verify(callableStatement).setArray(1, null); + verify(oracleConnection).createOracleArray(CustomTypes.UT_VARCHAR2_LIST, options.pathList.toArray()); + + assertThat(sql, containsString("a_reporters => ?")); + verify(callableStatement).setArray(2, null); + verify(oracleConnection).createOracleArray(CustomTypes.UT_REPORTERS, options.reporterList.toArray()); + + assertThat(sql, containsString("a_color_console => (case ? when 1 then true else false end)")); + verify(callableStatement).setInt(3, 0); + + assertThat(sql, containsString("a_coverage_schemes => ?")); + verify(callableStatement).setArray(4, null); + verify(oracleConnection).createOracleArray(CustomTypes.UT_VARCHAR2_LIST, options.coverageSchemes.toArray()); + + assertThat(sql, containsString("a_source_file_mappings => ?")); + verify(callableStatement).setArray(5, null); + + assertThat(sql, containsString("a_test_file_mappings => ?")); + verify(callableStatement).setArray(6, null); + verify(oracleConnection, times(2)).createOracleArray(CustomTypes.UT_FILE_MAPPINGS, expectedFileMapping); + + assertThat(sql, containsString("a_include_objects => ?")); + verify(callableStatement).setArray(7, null); + verify(oracleConnection).createOracleArray(CustomTypes.UT_VARCHAR2_LIST, options.includeObjects.toArray()); + + assertThat(sql, containsString("a_exclude_objects => ?")); + verify(callableStatement).setArray(8, null); + verify(oracleConnection).createOracleArray(CustomTypes.UT_VARCHAR2_LIST, options.includeObjects.toArray()); + } + + private void checkFailOnError(boolean shouldBeThere) throws SQLException { + String sql = testRunnerStatement.getSql(); + + assertThat(sql, doesOrDoesNotContainString("a_fail_on_errors => (case ? when 1 then true else false end)", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setInt(9, 1); + } + + private void checkClientCharacterSet(boolean shouldBeThere) throws SQLException { + String sql = testRunnerStatement.getSql(); + + assertThat(sql, doesOrDoesNotContainString("a_client_character_set => ?", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setString(10, "UTF8"); + } + + private void checkRandomTestOrder(boolean shouldBeThere) throws SQLException { + String sql = testRunnerStatement.getSql(); + + assertThat(sql, doesOrDoesNotContainString("a_random_test_order => (case ? when 1 then true else false end)", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setInt(11, 1); + assertThat(sql, doesOrDoesNotContainString("a_random_test_order_seed => ?", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setInt(12, 123); + } + + private void checkTags(boolean shouldBeThere) throws SQLException { + String sql = testRunnerStatement.getSql(); + + assertThat(sql, doesOrDoesNotContainString("a_tags => ?", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setString(13, "WIP,long_running"); + } + + private void checkExpr(boolean shouldBeThere) throws SQLException { + String sql = testRunnerStatement.getSql(); + + assertThat(sql, doesOrDoesNotContainString("a_include_schema_expr => ?", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setString(14, "a_*"); + assertThat(sql, doesOrDoesNotContainString("a_include_object_expr => ?", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setString(15, "a_*"); + assertThat(sql, doesOrDoesNotContainString("a_exclude_schema_expr => ?", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setString(16, "ut3:*_package*"); + assertThat(sql, doesOrDoesNotContainString("a_exclude_object_expr => ?", shouldBeThere)); + verify(callableStatement, doesOrDoesNotGetCalled(shouldBeThere)).setString(17, "ut3:*_package*"); + } +} diff --git a/src/test/java/org/utplsql/api/testRunner/FileMapperIT.java b/src/test/java/org/utplsql/api/testRunner/FileMapperIT.java new file mode 100644 index 0000000..5c7a306 --- /dev/null +++ b/src/test/java/org/utplsql/api/testRunner/FileMapperIT.java @@ -0,0 +1,87 @@ +package org.utplsql.api.testRunner; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.utplsql.api.AbstractDatabaseTest; +import org.utplsql.api.FileMapperOptions; +import org.utplsql.api.FileMapping; +import org.utplsql.api.KeyValuePair; +import org.utplsql.api.testRunner.FileMapper; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +class FileMapperIT extends AbstractDatabaseTest { + + @Test + void testFileMapper() throws SQLException { + List typeMappings = new ArrayList<>(); + typeMappings.add(new KeyValuePair("procedures", "PROCEDURE")); + typeMappings.add(new KeyValuePair("functions", "FUNCTION")); + + List filePaths = java.util.Arrays.asList( + "sources/app/procedures/award_bonus.sql", + "sources/app/functions/betwnstr.sql"); + + FileMapperOptions mapperOptions = new FileMapperOptions(filePaths); + mapperOptions.setObjectOwner("APP"); + mapperOptions.setTypeMappings(typeMappings); + mapperOptions.setRegexPattern("\\w+[\\\\\\/](\\w+)[\\\\\\/](\\w+)[\\\\\\/](\\w+)[.](\\w{3})"); + mapperOptions.setOwnerSubExpression(1); + mapperOptions.setTypeSubExpression(2); + mapperOptions.setNameSubExpression(3); + + List fileMappings = FileMapper.buildFileMappingList(getConnection(), mapperOptions); + + if (fileMappings.size() != 2) { + fail("Wrong mapping list size."); + } + + assertMapping(fileMappings.get(0), "APP", "AWARD_BONUS", "PROCEDURE"); + assertMapping(fileMappings.get(1), "APP", "BETWNSTR", "FUNCTION"); + } + + private void assertMapping(FileMapping fileMapping, String owner, String name, String type) { + assertEquals(owner, fileMapping.getObjectOwner()); + assertEquals(name, fileMapping.getObjectName()); + assertEquals(type, fileMapping.getObjectType()); + } + + @Nested + class Default_type_mapping { + + void checkTypeMapping( List typeMappings ) throws SQLException { + List filePaths = java.util.Arrays.asList( + "/award_bonus.prc", + "/betwnstr.fnc", + "/package_body.pkb", + "/type_body.tpb", + "/trigger.trg"); + FileMapperOptions mapperOptions = new FileMapperOptions(filePaths); + mapperOptions.setTypeMappings(typeMappings); + + List fileMappings = FileMapper.buildFileMappingList(getConnection(), mapperOptions); + + assertEquals("PROCEDURE", fileMappings.get(0).getObjectType()); + assertEquals("FUNCTION", fileMappings.get(1).getObjectType()); + assertEquals("PACKAGE BODY", fileMappings.get(2).getObjectType()); + assertEquals("TYPE BODY", fileMappings.get(3).getObjectType()); + assertEquals("TRIGGER", fileMappings.get(4).getObjectType()); + } + + @Test + void is_used_on_null_parameter() throws SQLException { + checkTypeMapping(null); + } + + @Test + void is_used_on_empty_parameter() throws SQLException { + checkTypeMapping(new ArrayList<>()); + } + } + +} diff --git a/src/test/java/org/utplsql/api/testRunner/TestRunnerStatementProviderIT.java b/src/test/java/org/utplsql/api/testRunner/TestRunnerStatementProviderIT.java new file mode 100644 index 0000000..2676c38 --- /dev/null +++ b/src/test/java/org/utplsql/api/testRunner/TestRunnerStatementProviderIT.java @@ -0,0 +1,106 @@ +package org.utplsql.api.testRunner; + +import org.junit.jupiter.api.Test; +import org.utplsql.api.AbstractDatabaseTest; +import org.utplsql.api.FileMapperOptions; +import org.utplsql.api.TestRunnerOptions; +import org.utplsql.api.Version; +import org.utplsql.api.reporter.CoreReporters; +import org.utplsql.api.reporter.ReporterFactory; + +import java.sql.SQLException; +import java.util.Arrays; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +class TestRunnerStatementProviderIT extends AbstractDatabaseTest { + + public static TestRunnerOptions getCompletelyFilledOptions() { + TestRunnerOptions options = new TestRunnerOptions(); + options.pathList.add("path"); + options.reporterList.add(ReporterFactory.createEmpty().createReporter(CoreReporters.UT_DOCUMENTATION_REPORTER.name())); + options.coverageSchemes.add("APP"); + options.sourceMappingOptions = new FileMapperOptions(Arrays.asList("sourcePath")); + options.testMappingOptions = new FileMapperOptions(Arrays.asList("testPath")); + options.includeObjects.add("include1"); + options.excludeObjects.add("exclude1"); + options.failOnErrors = true; + options.clientCharacterSet = "UTF8"; + options.randomTestOrder = true; + options.randomTestOrderSeed = 123; + options.tags.add("WIP"); + options.tags.add("long_running"); + options.includeSchemaExpr = "a_*"; + options.includeObjectExpr = "a_*"; + options.excludeSchemaExpr = "ut3:*_package*"; + options.excludeObjectExpr = "ut3:*_package*"; + return options; + } + + TestRunnerStatement getTestRunnerStatementForVersion(Version version) throws SQLException { + return TestRunnerStatementProvider.getCompatibleTestRunnerStatement(version, getCompletelyFilledOptions(), getConnection()); + } + + @Test + void testGettingPre303Version() throws SQLException { + TestRunnerStatement stmt = getTestRunnerStatementForVersion(Version.V3_0_2); + assertThat(stmt.getSql(), not(containsString("a_fail_on_errors"))); + assertThat(stmt.getSql(), not(containsString("a_client_character_set"))); + assertThat(stmt.getSql(), not(containsString("a_random_test_order"))); + assertThat(stmt.getSql(), not(containsString("a_random_test_order_seed"))); + assertThat(stmt.getSql(), not(containsString("a_tags"))); + } + + + @Test + void testGettingPre312Version_from_303() throws SQLException { + TestRunnerStatement stmt = getTestRunnerStatementForVersion(Version.V3_0_3); + assertThat(stmt.getSql(), containsString("a_fail_on_errors")); + assertThat(stmt.getSql(), not(containsString("a_client_character_set"))); + assertThat(stmt.getSql(), not(containsString("a_random_test_order"))); + assertThat(stmt.getSql(), not(containsString("a_random_test_order_seed"))); + assertThat(stmt.getSql(), not(containsString("a_tags"))); + } + + @Test + void testGettingPre312Version_from_311() throws SQLException { + TestRunnerStatement stmt = getTestRunnerStatementForVersion(Version.V3_1_1); + assertThat(stmt.getSql(), containsString("a_fail_on_errors")); + assertThat(stmt.getSql(), not(containsString("a_client_character_set"))); + assertThat(stmt.getSql(), not(containsString("a_random_test_order"))); + assertThat(stmt.getSql(), not(containsString("a_random_test_order_seed"))); + assertThat(stmt.getSql(), not(containsString("a_tags"))); + } + + @Test + void testGettingPre317Version_from_312() throws SQLException { + TestRunnerStatement stmt = getTestRunnerStatementForVersion(Version.V3_1_2); + assertThat(stmt.getSql(), containsString("a_fail_on_errors")); + assertThat(stmt.getSql(), containsString("a_client_character_set")); + assertThat(stmt.getSql(), not(containsString("a_random_test_order"))); + assertThat(stmt.getSql(), not(containsString("a_random_test_order_seed"))); + assertThat(stmt.getSql(), not(containsString("a_tags"))); + } + + @Test + void testGettingPre317Version_from_316() throws SQLException { + TestRunnerStatement stmt = getTestRunnerStatementForVersion(Version.V3_1_6); + assertThat(stmt.getSql(), containsString("a_fail_on_errors")); + assertThat(stmt.getSql(), containsString("a_client_character_set")); + assertThat(stmt.getSql(), not(containsString("a_random_test_order"))); + assertThat(stmt.getSql(), not(containsString("a_random_test_order_seed"))); + assertThat(stmt.getSql(), not(containsString("a_tags"))); + } + + @Test + void testGettingActualVersion_from_latest() throws SQLException { + TestRunnerStatement stmt = getTestRunnerStatementForVersion(Version.LATEST); + assertThat(stmt.getSql(), containsString("a_fail_on_errors")); + assertThat(stmt.getSql(), containsString("a_client_character_set")); + assertThat(stmt.getSql(), containsString("a_random_test_order")); + assertThat(stmt.getSql(), containsString("a_random_test_order_seed")); + assertThat(stmt.getSql(), containsString("a_tags")); + } +} diff --git a/src/test/java/org/utplsql/api/testRunner/TestRunnerStatementProviderTest.java b/src/test/java/org/utplsql/api/testRunner/TestRunnerStatementProviderTest.java deleted file mode 100644 index 5c0c733..0000000 --- a/src/test/java/org/utplsql/api/testRunner/TestRunnerStatementProviderTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.utplsql.api.testRunner; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.utplsql.api.TestRunnerOptions; -import org.utplsql.api.Version; -import org.utplsql.api.rules.DatabaseRule; - -import java.sql.SQLException; - -public class TestRunnerStatementProviderTest { - - @Rule - public final DatabaseRule db = new DatabaseRule(); - - @Test - public void testGettingPre303Version() { - try { - TestRunnerStatement stmt = TestRunnerStatementProvider.getCompatibleTestRunnerStatement(new Version("3.0.2"), new TestRunnerOptions(), db.newConnection()); - - Assert.assertEquals(Pre303TestRunnerStatement.class, stmt.getClass()); - } catch (SQLException e) { - e.printStackTrace(); - Assert.fail(); - } - } - - - @Test - public void testGettingActualVersion() { - try { - TestRunnerStatement stmt = TestRunnerStatementProvider.getCompatibleTestRunnerStatement(new Version("3.0.3"), new TestRunnerOptions(), db.newConnection()); - - Assert.assertEquals(ActualTestRunnerStatement.class, stmt.getClass()); - } catch (SQLException e) { - e.printStackTrace(); - Assert.fail(); - } - } -} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy