diff --git a/.gitattributes b/.gitattributes index abd1d5471..2cda5ad55 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,17 +1,18 @@ +.github export-ignore +.idea export-ignore +development export-ignore +test export-ignore .gitattributes export-ignore .gitignore export-ignore .gitmodules export-ignore -.travis.yml export-ignore mkdocs.yml export-ignore -.travis export-ignore -.github export-ignore +BUILD_NO export-ignore +mkdocs_offline.yml export-ignore sonar-project.properties export-ignore -tests export-ignore -development export-ignore -node_modules export-ignore -^docs/* linguist-documentation +^docs/** linguist-documentation *.pkb linguist-language=PLSQL *.pks linguist-language=PLSQL *.sql linguist-language=PLSQL *.tpb linguist-language=PLSQL *.tps linguist-language=PLSQL +*.sh text eol=lf diff --git a/.github/scripts/get_project_build_version.sh b/.github/scripts/get_project_build_version.sh deleted file mode 100755 index 838523b05..000000000 --- a/.github/scripts/get_project_build_version.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo `sed -E "s/(v?[0-9]+\.)([0-9]+\.)([0-9]+)(-.*)?/\1\2\3\.${UTPLSQL_BUILD_NO}\4/" <<< "${UTPLSQL_VERSION}"` diff --git a/.github/scripts/get_project_version.sh b/.github/scripts/get_project_version.sh index 60fc0a796..54a30a562 100755 --- a/.github/scripts/get_project_version.sh +++ b/.github/scripts/get_project_version.sh @@ -1,14 +1,7 @@ #!/usr/bin/env bash - -#When building a new version from a release branch, the version is taken from release branch name -if [[ "${CI_ACTION_REF_NAME}" =~ ^release/v[0-9]+\.[0-9]+\.[0-9]+.*$ ]]; then - version=${CI_ACTION_REF_NAME#release\/} -else - #Otherwise, version is taken from the VERSION file - version=`cat VERSION` - #When on develop branch, add "-develop" to the version text - if [[ "${CI_ACTION_REF_NAME}" == "develop" ]]; then +version=`cat VERSION` +#When on develop branch, add "-develop" to the version text +if [[ "${CI_ACTION_REF_NAME}" == "develop" ]]; then version=`sed -E "s/(v?[0-9]+\.[0-9]+\.[0-9]+).*/\1-develop/" <<< "${version}"` - fi fi echo ${version} diff --git a/.github/scripts/set_release_version_numbers_env.sh b/.github/scripts/set_release_version_numbers_env.sh new file mode 100755 index 000000000..a43843508 --- /dev/null +++ b/.github/scripts/set_release_version_numbers_env.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +build_no=$(cat BUILD_NO) +version=${CI_ACTION_REF_NAME} + +echo "UTPLSQL_BUILD_NO=${build_no}" >> $GITHUB_ENV +echo "UTPLSQL_VERSION=${version}" >> $GITHUB_ENV +echo UTPLSQL_BUILD_VERSION=$(echo ${version} | sed -E "s/(v?[0-9]+\.)([0-9]+\.)([0-9]+)(-.*)?/\1\2\3\.${build_no}\4/") >> $GITHUB_ENV + diff --git a/.github/scripts/set_version_numbers_env.sh b/.github/scripts/set_version_numbers_env.sh index 10529a1bf..c61639bf9 100755 --- a/.github/scripts/set_version_numbers_env.sh +++ b/.github/scripts/set_version_numbers_env.sh @@ -1,10 +1,8 @@ #!/bin/bash -UTPLSQL_BUILD_NO=$( expr ${GITHUB_RUN_NUMBER} + ${UTPLSQL_BUILD_NO_OFFSET} ) -UTPLSQL_VERSION=$(.github/scripts/get_project_version.sh) +build_no=$( expr ${GITHUB_RUN_NUMBER} + ${UTPLSQL_BUILD_NO_OFFSET} ) +version=$(.github/scripts/get_project_version.sh) -echo "UTPLSQL_BUILD_NO=${UTPLSQL_BUILD_NO}" >> $GITHUB_ENV -echo "UTPLSQL_VERSION=${UTPLSQL_VERSION}" >> $GITHUB_ENV -echo UTPLSQL_BUILD_VERSION=$(echo ${UTPLSQL_VERSION} | sed -E "s/(v?[0-9]+\.)([0-9]+\.)([0-9]+)(-.*)?/\1\2\3\.${UTPLSQL_BUILD_NO}\4/") >> $GITHUB_ENV - -echo "CURRENT_BRANCH=${CI_ACTION_REF_NAME}" >> $GITHUB_ENV +echo "UTPLSQL_BUILD_NO=${build_no}" >> $GITHUB_ENV +echo "UTPLSQL_VERSION=${version}" >> $GITHUB_ENV +echo UTPLSQL_BUILD_VERSION=$(echo ${version} | sed -E "s/(v?[0-9]+\.)([0-9]+\.)([0-9]+)(-.*)?/\1\2\3\.${build_no}\4/") >> $GITHUB_ENV diff --git a/.github/scripts/update_project_version.sh b/.github/scripts/update_project_version.sh index 586cb5b64..66e45125a 100755 --- a/.github/scripts/update_project_version.sh +++ b/.github/scripts/update_project_version.sh @@ -16,4 +16,5 @@ sed -i -r "s/(sonar\.projectVersion=).*?/\1${UTPLSQL_VERSION}/" sonar-project.pr echo Update VERSION file echo ${UTPLSQL_VERSION} > VERSION +echo ${UTPLSQL_BUILD_NO} > BUILD_NO diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0aae6b64..af2e4875d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,6 +58,11 @@ jobs: oracle-sid: 'XE' oracle-version: "gvenzl/oracle-xe:21-slim" oracle-base: '/opt/oracle' + - id: 7 + db_version_name: '23free' + oracle-sid: 'FREEPDB1' + oracle-version: "gvenzl/oracle-free:23-slim" + oracle-base: '/opt/oracle' services: html_checker: @@ -83,7 +88,7 @@ jobs: --health-cmd healthcheck.sh steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: c-py/action-dotenv-to-setenv@v2 @@ -167,7 +172,7 @@ jobs: - name: Upload ORACLE_BASE/diag data Artifact id: upload if: ${{ always() && steps.run-tests.outcome == 'failure' }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: my-artifact$-${{matrix.db_version_name}} path: ${{github.workspace}}/database-diag @@ -178,7 +183,7 @@ jobs: run: bash .github/scripts/validate_report_files.sh - name: Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos files: ./cobertura.xml @@ -213,8 +218,7 @@ jobs: API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} if: | github.repository == 'utPLSQL/utPLSQL' && - github.base_ref == null && - ( startsWith( github.ref, 'refs/heads/release/v' ) || github.ref == 'refs/heads/develop' ) + github.base_ref == null && github.ref == 'refs/heads/develop' steps: - name: 🔍 API_TOKEN_GITHUB if: env.API_TOKEN_GITHUB == '' @@ -241,15 +245,22 @@ jobs: - name: Push version update to repository id: push-version-number-update run: | - git add sonar-project.properties VERSION source/* docs/* + git add sonar-project.properties VERSION BUILD_NO source/* docs/* git commit -m 'Updated project version after build [skip ci]' git push --quiet origin HEAD:${CI_ACTION_REF_NAME} - - name: Copy and push documentation to utPLSQL-github-io repo - id: push-documentation - env: - API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} - run: .github/scripts/push_docs_to_github_io.sh + - name: Setup git config + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + + - name: Build and publish documentation + run: | + pip install mkdocs + pip install mkdocs-git-revision-date-localized-plugin + pip install mkdocs-material + pip install git+https://github.com/jimporter/mike.git + mike deploy -p develop dispatch: name: Dispatch downstream builds diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd977d447..1c4db5d59 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,43 +10,13 @@ defaults: jobs: - publish: - name: Deploy documentation - concurrency: publish - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: c-py/action-dotenv-to-setenv@v2 - with: - env-file: .github/variables/.env - - uses: FranzDiebold/github-env-vars-action@v2 #https://github.com/marketplace/actions/github-environment-variables-action - - - name: Set buid version number env variables - run: .github/scripts/set_version_numbers_env.sh - - - name: Setup git config - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - - - name: Update project version & build number in source code and documentation - run: .github/scripts/update_project_version.sh - - - name: Copy and push documentation to utPLSQL-github-io repo - env: - API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} - run: .github/scripts/push_docs_to_github_io.sh - upload_artifacts: name: Upload archives concurrency: upload runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: c-py/action-dotenv-to-setenv@v2 @@ -54,8 +24,8 @@ jobs: env-file: .github/variables/.env - uses: FranzDiebold/github-env-vars-action@v2 #https://github.com/marketplace/actions/github-environment-variables-action - - name: Set buid version number env variables - run: .github/scripts/set_version_numbers_env.sh + - name: Set build version number env variables + run: .github/scripts/set_release_version_numbers_env.sh - name: Update project version & build number in source code and documentation run: .github/scripts/update_project_version.sh @@ -65,10 +35,14 @@ jobs: git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" - - name: Build html documentation + - name: Build and publish documentation run: | pip install mkdocs - mkdocs build --clean --strict + pip install mkdocs-git-revision-date-localized-plugin + pip install mkdocs-material + pip install git+https://github.com/jimporter/mike.git + mike deploy -p -u ${UTPLSQL_VERSION} latest + mkdocs build --clean -f mkdocs_offline.yml rm -rf docs/* cp -r -v site/* docs git add . @@ -93,7 +67,7 @@ jobs: slack-workflow-status: if: always() name: Post Workflow Status To Slack - needs: [ publish, upload_artifacts ] + needs: [ upload_artifacts ] runs-on: ubuntu-latest steps: - name: Slack Workflow Notification diff --git a/BUILD_NO b/BUILD_NO new file mode 100644 index 000000000..fab85b21d --- /dev/null +++ b/BUILD_NO @@ -0,0 +1 @@ +4206 diff --git a/VERSION b/VERSION index d0237db61..5fcdcb942 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v3.1.13-develop +v3.1.14-develop diff --git a/development/cleanup.sh b/development/cleanup.sh index 33358302a..65f3ab0c1 100755 --- a/development/cleanup.sh +++ b/development/cleanup.sh @@ -1,9 +1,9 @@ -#!/usr/bin/env bash +#!/bin/bash #goto git root directory git rev-parse && cd "$(git rev-parse --show-cdup)" -. development/env.sh +. ./development/env.sh "${SQLCLI}" sys/${ORACLE_PWD}@//${CONNECTION_STR} AS SYSDBA <<-SQL set echo on diff --git a/development/install.sh b/development/install.sh index f3a62b0ec..468bafd38 100755 --- a/development/install.sh +++ b/development/install.sh @@ -1,9 +1,9 @@ -#!/usr/bin/env bash +#!/bin/bash #goto git root directory git rev-parse && cd "$(git rev-parse --show-cdup)" -. development/env.sh +. ./development/env.sh header="******************************************************************************************" if ! development/cleanup.sh; then diff --git a/development/refresh_sources.sh b/development/refresh_sources.sh index 7af3aaec0..83518d7cc 100755 --- a/development/refresh_sources.sh +++ b/development/refresh_sources.sh @@ -1,9 +1,9 @@ -#!/usr/bin/env bash +#!/bin/bash #goto git root directory git rev-parse && cd "$(git rev-parse --show-cdup)" -. development/env.sh +. ./development/env.sh # remove sub-direcotry containing main branch shallow copy rm -rf ${UTPLSQL_DIR:-utPLSQL_latest_release} @@ -12,7 +12,7 @@ git clone --depth=1 --branch=${SELFTESTING_BRANCH:-main} https://github.com/utPL rm -rf utPLSQL-cli/* # download latest release version of utPLSQL-cli -curl -Lk -o utPLSQL-cli.zip https://github.com/utPLSQL/utPLSQL-cli/releases/download/v${UTPLSQL_CLI_VERSION}/utPLSQL-cli.zip +curl -Lk -o utPLSQL-cli.zip https://github.com/utPLSQL/utPLSQL-cli/releases/download/${UTPLSQL_CLI_VERSION}/utPLSQL-cli.zip # unzip utPLSQL-cli and remove the zip file unzip utPLSQL-cli.zip && chmod u+x utPLSQL-cli/bin/utplsql && rm utPLSQL-cli.zip diff --git a/development/refresh_ut3.sh b/development/refresh_ut3.sh index 570d3c86a..827af827d 100755 --- a/development/refresh_ut3.sh +++ b/development/refresh_ut3.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash #goto git root directory git rev-parse && cd "$(git rev-parse --show-cdup)" diff --git a/development/releasing.md b/development/releasing.md index f85e18c3c..ac2091632 100644 --- a/development/releasing.md +++ b/development/releasing.md @@ -1,29 +1,28 @@ ## Release process -With every build, the build process on Travis updates files with an appropriate version number before deployment into the database. -This step is performed, to confirm that the update of versions works properly. +To create a release follow the below steps -## To create a release +## Release preparation + - Create a **draft** of a Release with a new tag number `vX.Y.X` sourced from the `develop` branch on [github releases page](https://github.com/utPLSQL/utPLSQL/releases) + - Populate release description using the `Generate release notes` button + - Review the auto-generated release notes and update tem if needed + - Optionally, split the default `## What's Changed` list into `## New features`, `## Enhancements`, `## Bug fixes`. See previous release notes for details - - create release branch from development branch and make sure to name the release branch: `release/vX.Y.Z` - - update, commit and push at least one file change in the release branch, to kickoff a Travis build - - wait for th build to complete successfully - - merge the release branch to main and wait for main build to complete successfully (do not use Squash/rebase for merge operation) - - create a Github release from the main branch using [github releases page](https://github.com/utPLSQL/utPLSQL/releases) and populate release description using information found on the issues and pull requests since previous release. - To find issues closed after certain date use [advanced filters](https://help.github.com/articles/searching-issues-and-pull-requests/#search-by-open-or-closed-state). - Example: [`is:issue closed:>2018-07-22`](https://github.com/utPLSQL/utPLSQL/issues?utf8=%E2%9C%93&q=is%3Aissue+closed%3A%3E2018-07-22+) - - After A build was completed on a TAG (github release) was successful, merge main branch back into develop branch. - - At this point, main branch and release tag should be at the same commit version and artifacts should be uploaded into Github release. - - After develop branch was built, update version number in `VERSION` file to represent next planned release version. - - Clone `utplsql.githug.io` project and add a new announcement about next version being released in `_posts`. Use previous announcements as a template. Make sure to set date, time and post title properly. +## Performing a release + - Publish [the previously prepared](#release-preparation) release draft. + - Wait for the [Github Actions `Release`](https://github.com/utPLSQL/utPLSQL/actions/workflows/release.yml) process to complete successfully. The process will upload release artifacts (`zip` and `tar.gz` files along with `md5`) + - After Release build was completed successfully, merge the `develop` branch into `main` branch. At this point, main branch and release tag should be at the same commit version and artifacts should be uploaded into Github release. + - Increase the version number in the `VERSION` file on `develop` branch to open start next release version. + - Clone `utplsql.githug.io` project and: + - Add a new announcement about next version being released in `docs/_posts`. Use previous announcements as a template. Make sure to set date, time and post title properly. + - Add the post to list in `mkdocs.yml` file in root directory of that repository. + - Add the link to the post at the beginning of the `docs/index.md` file. + - Send the announcement on Twitter(X) accoiunt abut utPLSQL release. The following will happen: - - build executed on branch `release/vX.Y.Z-[something]` updates files `sonar-project.properties`, `VERSION` with project version derived from the release branch name - - changes to those two files are committed and pushed back to release branch by Travis - - builds on main branch are **not getting executed** - - when a Github release is created, a new tag is added in on the repository and a tag build is executed - - the documentation for new release is published on `utplsql.github.io` and installation archives are added to the tag. + - When a Github release is published, a new tag is added in on the repository and a release build is executed + - With Release action, the documentation for new release is published on `utplsql.github.io` and installation archives are added to the release. -Note: -The sources for release are provided in separate zip files delivered from the Travis build process. -The built zip files include HTML documentation generated from MD files. +# Note: +The utPLSQL installation files are uploaded by the release build process as release artifacts (separate `zip` and `tar.gz` files). +The release artifacts include HTML documentation generated from MD files, sources and tests diff --git a/development/template.env.sh b/development/template.env.sh index e69aa81aa..7761a70c4 100755 --- a/development/template.env.sh +++ b/development/template.env.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash export SQLCLI=sql # For sqlcl client #export SQLCLI=sqlplus # For sqlplus client diff --git a/docs/about/authors.md b/docs/about/authors.md index 1de762d74..499398993 100644 --- a/docs/about/authors.md +++ b/docs/about/authors.md @@ -1,4 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) ### utPLSQL v3 Major Contributors diff --git a/docs/about/license.md b/docs/about/license.md index 5030fff0a..03cc286eb 100644 --- a/docs/about/license.md +++ b/docs/about/license.md @@ -1,4 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) # Version Information diff --git a/docs/about/project-details.md b/docs/about/project-details.md index 71178988b..5579a943b 100644 --- a/docs/about/project-details.md +++ b/docs/about/project-details.md @@ -1,4 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) # utPLSQL Project Details diff --git a/docs/about/support.md b/docs/about/support.md index 1c0679440..6d68864bd 100644 --- a/docs/about/support.md +++ b/docs/about/support.md @@ -1,4 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) # How to get support diff --git a/docs/assets/favicon.png b/docs/assets/favicon.png new file mode 100644 index 000000000..7c34109d0 Binary files /dev/null and b/docs/assets/favicon.png differ diff --git a/docs/assets/icon-transparent.png b/docs/assets/icon-transparent.png new file mode 100644 index 000000000..6e4534aad Binary files /dev/null and b/docs/assets/icon-transparent.png differ diff --git a/docs/index.md b/docs/index.md index b79c845f5..b277c9d16 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,39 +1,23 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) -# Introduction to utPLSQL +## What is utPLSQL utPLSQL is a Unit Testing framework for Oracle PL/SQL. The framework follows industry standards and best patterns of modern Unit Testing frameworks like [JUnit](http://junit.org/junit4/) and [RSpec](http://rspec.info/) + +## Demo project + +Have a look at [utPLSQL demo project](https://github.com/utPLSQL/utPLSQL-demo-project/) to see: + +- sample code and tests +- demo of deployment automation that leverages: + - Flyway / Liquidbase for scripting and deployment of DB changes + - Docker container with Oracle XE Database + - GitHub Actions and Azure Pipelines to orchestrate the deployment and testing process + - utPLSQL framework for writhing, execution of tests as well as reporting test results and code coverage + - [Sonar]((https://sonarcloud.io/project/overview?id=utPLSQL:utPLSQL-demo-project).) for code quality gate, test results and code coverage reporting - - User Guide - - [Installation](userguide/install.md) - - [Getting Started](userguide/getting-started.md) - - [Annotations](userguide/annotations.md) - - [Expectations](userguide/expectations.md) - - [Advanced data comparison](userguide/advanced_data_comparison.md) - - [Running unit tests](userguide/running-unit-tests.md) - - [Querying for test suites](userguide/querying_suites.md) - - [Testing best practices](userguide/best-practices.md) - - [Upgrade utPLSQL](userguide/upgrade.md) - - Reporting - - [Using reporters](userguide/reporters.md) - - [Reporting errors](userguide/exception-reporting.md) - - [Code coverage](userguide/coverage.md) - - [Cheat-sheet](https://www.cheatography.com/jgebal/cheat-sheets/utplsql-v3-1-2/#downloads) - - About - - [Project Details](about/project-details.md) - - [License](about/license.md) - - [Support](about/support.md) - - [Authors](about/authors.md) - - [Version 2 to Version 3 Comparison](compare_version2_to_3.md) - -# Demo project - -Have a look at our [demo project](https://github.com/utPLSQL/utPLSQL-demo-project/). - -It uses [Travis CI](https://travis-ci.org/utPLSQL/utPLSQL-demo-project) to build on every commit, runs all tests, publishes test results and code coverage to [SonarCloud](https://sonarcloud.io/project/overview?id=utPLSQL:utPLSQL-demo-project). - -# Three steps +## Three steps With just three simple steps you can define and run your unit tests for PLSQL code. @@ -48,11 +32,12 @@ Here is how you can simply create tested code, unit tests and execute the tests Check out the sections on [annotations](userguide/annotations.md) and [expectations](userguide/expectations.md) to see how to define your tests. -# Command line +## Command line You can use the utPLSQL command line client [utPLSQL-cli](https://github.com/utPLSQL/utPLSQL-cli) to run tests without the need for Oracle Client or any IDE like SQLDeveloper/TOAD etc. Amongst many benefits they provide ability to: + * see the progress of test execution for long-running tests - real-time reporting * use many reporting formats simultaneously and save reports to files (publish) * map your project source files and test files into database objects @@ -60,10 +45,9 @@ Amongst many benefits they provide ability to: Download the [latest client](https://github.com/utPLSQL/utPLSQL-cli/releases/latest) and you are good to go. See [project readme](https://github.com/utPLSQL/utPLSQL-cli/blob/develop/README.md) for details. -# Coverage - -If you want to have code coverage gathered on your code , it's best to use `ut_run` to execute your tests with multiple reporters and have both test execution report as well as coverage report saved to a file. +## Coverage +It is best to use utPLSQL-cli or execute tests and gather code coverage from command line. Check out the [coverage documentation](userguide/coverage.md) for options of coverage reporting diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 000000000..7f05ab831 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,12 @@ +[data-md-color-scheme="default"] { + --md-primary-fg-color: #2f8bff; + --md-accent-fg-color: #1f5db0; + --md-accent-fg-color--transparent: #1f5db0; +} + +[data-md-color-scheme="slate"] { + --md-hue: 200; + --md-primary-fg-color: #2f8bff; + --md-accent-fg-color: #1f5db0; + --md-accent-fg-color--transparent: #1f5db0; +} diff --git a/docs/userguide/advanced_data_comparison.md b/docs/userguide/advanced_data_comparison.md index 2faf98dec..45fca1871 100644 --- a/docs/userguide/advanced_data_comparison.md +++ b/docs/userguide/advanced_data_comparison.md @@ -1,4 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) # Advanced data comparison @@ -48,7 +48,7 @@ When specifying column/attribute names, keep in mind that the names are **case s ## Excluding elements from data comparison Consider the following examples -```sql +```sql linenums="1" declare l_expected sys_refcursor; l_actual sys_refcursor; @@ -86,7 +86,7 @@ The actual data is equal/contains expected, when those columns are excluded. ## Selecting columns for data comparison Consider the following example -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -125,7 +125,7 @@ The actual data is equal/contains expected, when only those columns are included You can chain the advanced options in an expectation and mix the `varchar2` with `ut_varchar2_list` arguments. When doing so, the final list of items to include/exclude will be a concatenation of all items. -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -163,7 +163,7 @@ SUCCESS Example of `include / exclude` for anydata.convertCollection -```sql +```sql linenums="1" create or replace type person as object( name varchar2(100), age integer @@ -219,7 +219,7 @@ Unordered option allows for quick comparison of two compound data types without Result of such comparison will be limited to only information about row existing or not existing in given set without actual information about exact differences. -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -267,7 +267,7 @@ The extra or missing rows will be presented to user as well as all non-matching Join by option can be used in conjunction with include or exclude options. However if any of the join keys is part of exclude set, comparison will fail and report to user that sets could not be joined on specific key, as the key was excluded. -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -305,7 +305,7 @@ FAILURE You can use `join_by` syntax in combination with `contain` matcher. -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -322,7 +322,7 @@ end; ``` Above test will indicate that in actual data-set -```sql +```sql linenums="1" FAILURE Actual: refcursor [ count = 28 ] was expected to contain: refcursor [ count = 29 ] Diff: @@ -335,7 +335,7 @@ FAILURE You can specify multiple columns in `join_by` -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -371,7 +371,7 @@ To reference attribute as PK, use slash symbol `/` to separate nested elements. In the below example, cursors are joined using the `NAME` attribute of object in column `SOMEONE` -```sql +```sql linenums="1" create or replace type person as object( name varchar2(100), age integer @@ -414,7 +414,7 @@ FAILURE > `join_by` does not support joining on individual elements of nested table. You can still use data of the nested table as a PK value. > When collection is referenced in `join_by`, test will fail with appropriate message, as it cannot perform a join. -```sql +```sql linenums="1" create or replace type person as object( name varchar2(100), age integer @@ -463,7 +463,7 @@ You may provide items for `include`/`exclude`/`join_by` as a a ut_varchar2_list - nested table and varray items type attributes are nested under `` elements Example of a valid parameter to include columns: `RN`, `A_Column`, `SOME_COL` in data comparison. -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -494,7 +494,7 @@ Expectations that compare compound data type data with `unordered_columns` optio This option can be useful whn we have no control over the ordering of the column or the column order is not of importance from testing perspective. -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index 933b1b119..31a91cac2 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1,26 +1,25 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -# Annotations +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) Annotations are used to configure tests and suites in a declarative way similar to modern OOP languages. This way, test configuration is stored along with the test logic inside the test package. No additional configuration files or tables are needed for test cases. The annotation names are based on popular testing frameworks such as JUnit. The framework runner searches for all the suitable annotated packages, automatically configures suites, forms the suite hierarchy, executes it and reports results in specified formats. Annotation is defined by: + - single line comment `--` (double hyphen) - followed directly by a `%` (percent) - followed by annotation name - followed by optional annotation text placed in single brackets. -All of text between first opening bracket and last closing bracket in annotation line is considered to be annotation text +All text between first opening bracket and last closing bracket in annotation line is considered to be annotation text -Examples: -`--%suite(The name of my test suite)` - represents `suite` annotation with text `The name of my test suite` +For example `--%suite(The name of my test suite)` represents `suite` annotation with `The name of my test suite` as annotation text. -utPLSQL interprets the whole line of annotation and will treat all the text from the first opening bracket in the line to the last closing bracket +utPLSQL interprets the whole line of annotation and will treat text from the first opening bracket in the line to the last closing bracket in that line as annotation text -Example: - `--%suite(Stuff) -- we should name this ( correctly )` - represents `suite` annotation with text `Stuff) -- we should name this ( correctly ` +In below example we have a `suite` annotation with `Stuff) -- we should name this ( correctly ` as the annotation text + +`--%suite(Stuff) -- we should name this ( correctly )` Do not place comments within annotation line to avoid unexpected behaviors. @@ -38,7 +37,7 @@ There **can not** be any empty lines or comments between annotation line and pro There can be many annotations for a procedure. Valid procedure annotations example: -```sql +```sql linenums="1" package test_package is --%suite @@ -59,7 +58,7 @@ end; ``` Invalid procedure annotations examples: -```sql +```sql linenums="1" package test_package is --%suite @@ -90,7 +89,7 @@ Those annotations placed at any place in package except directly before procedur We strongly recommend putting package level annotations at the very top of package except for the `--%context` annotations (described below) Valid package annotations example: -```sql +```sql linenums="1" package test_package is --%suite @@ -108,7 +107,7 @@ end; ``` Invalid package annotations examples: -```sql +```sql linenums="1" package test_package is --%suite --This is wrong as suite annotation is not a procedure annotation procedure irrelevant; @@ -122,29 +121,29 @@ end; ## Supported annotations -| Annotation |Level| Description | -| --- | --- | --- | -| `--%suite()` | Package | Mandatory. Marks package as a test suite. Optional suite description can be provided (see `displayname`). | -| `--%suitepath()` | Package | Similar to java package. The annotation allows logical grouping of suites into hierarchies. | -| `--%displayname()` | Package/procedure | Human-readable and meaningful description of a context/suite/test. Overrides the `` provided with `suite`/`test`/`context` annotation. This annotation is redundant and might be removed in future releases. | -| `--%test()` | Procedure | Denotes that the annotated procedure is a unit test procedure. Optional test description can be provided (see `displayname`). | -| `--%throws([,...])`| Procedure | Denotes that the annotated test procedure must throw one of the exceptions provided. Supported forms of exceptions are: numeric literals, numeric constant names, exception constant names, predefined Oracle exception names. | -| `--%beforeall` | Procedure | Denotes that the annotated procedure should be executed once before all elements of the suite. | -| `--%beforeall([[.].][,...])` | Package | Denotes that the mentioned procedure(s) should be executed once before all elements of the suite. | -| `--%afterall` | Procedure | Denotes that the annotated procedure should be executed once after all elements of the suite. | -| `--%afterall([[.].][,...])` | Package | Denotes that the mentioned procedure(s) should be executed once after all elements of the suite. | -| `--%beforeeach` | Procedure | Denotes that the annotated procedure should be executed before each `%test` procedure in the suite. | -| `--%beforeeach([[.].][,...])` | Package | Denotes that the mentioned procedure(s) should be executed before each `%test` procedure in the suite. | -| `--%aftereach` | Procedure | Denotes that the annotated procedure should be executed after each `%test` procedure in the suite. | -| `--%aftereach([[.].][,...])` | Package | Denotes that the mentioned procedure(s) should be executed after each `%test` procedure in the suite. | -| `--%beforetest([[.].][,...])` | Procedure | Denotes that mentioned procedure(s) should be executed before the annotated `%test` procedure. | -| `--%aftertest([[.].][,...])` | Procedure | Denotes that mentioned procedure(s) should be executed after the annotated `%test` procedure. | -| `--%rollback()` | Package/procedure | Defines transaction control. Supported values: `auto`(default) - a savepoint is created before invocation of each "before block" is and a rollback to specific savepoint is issued after each "after" block; `manual` - rollback is never issued automatically. Property can be overridden for child element (test in suite) | -| `--%disabled()` | Package/procedure | Used to disable a suite, whole context or a test. Disabled suites/contexts/tests do not get executed, they are however marked and reported as disabled in a test run. The reason that will be displayed next to disabled tests is decided based on hierarchy suites -> context -> test | -| `--%context()` | Package | Denotes start of a named context (sub-suite) in a suite package an optional description for context can be provided. | -| `--%name()` | Package | Denotes name for a context. Must be placed after the context annotation and before start of nested context. | -| `--%endcontext` | Package | Denotes end of a nested context (sub-suite) in a suite package | -| `--%tags` | Package/procedure | Used to label a test or a suite for purpose of identification | +| Annotation | Level | Description | +|------------------------------------------------------------|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--%suite( )` | Package | Mandatory. Marks package as a test suite. Optional suite description can be provided (see `displayname`). | +| `--%suitepath( )` | Package | Similar to java package. The annotation allows logical grouping of suites into hierarchies. | +| `--%displayname( )` | Package/procedure | Human-readable and meaningful description of a context/suite/test. Overrides the `` provided with `suite`/`test`/`context` annotation. This annotation is redundant and might be removed in future releases. | +| `--%test( )` | Procedure | Denotes that the annotated procedure is a unit test procedure. Optional test description can be provided (see `displayname`). | +| `--%throws( [,...] )` | Procedure | Denotes that the annotated test procedure must throw one of the exceptions provided. Supported forms of exceptions are: numeric literals, numeric constant names, exception constant names, predefined Oracle exception names. | +| `--%beforeall` | Procedure | Denotes that the annotated procedure should be executed once before all elements of the suite. | +| `--%beforeall( [[.].][,...] )` | Package | Denotes that the mentioned procedure(s) should be executed once before all elements of the suite. | +| `--%afterall` | Procedure | Denotes that the annotated procedure should be executed once after all elements of the suite. | +| `--%afterall( [[.].][,...] )` | Package | Denotes that the mentioned procedure(s) should be executed once after all elements of the suite. | +| `--%beforeeach` | Procedure | Denotes that the annotated procedure should be executed before each `%test` procedure in the suite. | +| `--%beforeeach( [[.].][,...] )` | Package | Denotes that the mentioned procedure(s) should be executed before each `%test` procedure in the suite. | +| `--%aftereach` | Procedure | Denotes that the annotated procedure should be executed after each `%test` procedure in the suite. | +| `--%aftereach( [[.].][,...] )` | Package | Denotes that the mentioned procedure(s) should be executed after each `%test` procedure in the suite. | +| `--%beforetest( [[.].][,...] )` | Procedure | Denotes that mentioned procedure(s) should be executed before the annotated `%test` procedure. | +| `--%aftertest( [[.].][,...] )` | Procedure | Denotes that mentioned procedure(s) should be executed after the annotated `%test` procedure. | +| `--%rollback( )` | Package/procedure | Defines transaction control. Supported values: `auto`(default) - a savepoint is created before invocation of each "before block" is and a rollback to specific savepoint is issued after each "after" block; `manual` - rollback is never issued automatically. Property can be overridden for child element (test in suite) | +| `--%disabled( )` | Package/procedure | Used to disable a suite, whole context or a test. Disabled suites/contexts/tests do not get executed, they are however marked and reported as disabled in a test run. The reason that will be displayed next to disabled tests is decided based on hierarchy suites -> context -> test | +| `--%context( )` | Package | Denotes start of a named context (sub-suite) in a suite package an optional description for context can be provided. | +| `--%name( )` | Package | Denotes name for a context. Must be placed after the context annotation and before start of nested context. | +| `--%endcontext` | Package | Denotes end of a nested context (sub-suite) in a suite package | +| `--%tags` | Package/procedure | Used to label a test or a suite for purpose of identification | ### Suite @@ -164,13 +163,13 @@ If the parameters are placed without brackets or with incomplete brackets, they Suite package without description. -```sql +```sql linenums="1" create or replace package test_package as --%suite end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -181,13 +180,13 @@ Finished in .002415 seconds ``` Suite package with description. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -198,14 +197,14 @@ Finished in .001646 seconds ``` When multiple `--%suite` annotations are specified in package, the first annotation will be used and a warning message will appear indicating duplicate annotation. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) --%suite(Bad annotation) end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -223,14 +222,14 @@ Finished in .003318 seconds ``` When `--%suite` annotation is bound to procedure, it is ignored and results in package not getting recognized as test suite. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) procedure some_proc; end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -257,7 +256,7 @@ If `--%test` raises an unhandled exception the following will happen: - test execution will continue uninterrupted for rest of the suite Test procedure without description. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -270,7 +269,7 @@ create or replace package body test_package as end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -282,7 +281,7 @@ Finished in .004109 seconds ``` Test procedure with description. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -296,7 +295,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -308,7 +307,7 @@ Finished in .006828 seconds ``` When multiple `--%test` annotations are specified for a procedure, the first annotation will be used and a warning message will appear indicating duplicate annotation. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -323,7 +322,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -346,7 +345,7 @@ Marks annotated suite package or test procedure as disabled. You can provide the reason why the test is disabled that will be displayed in output. Disabling suite. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) --%disabled(Reason for disabling suite) @@ -367,7 +366,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -380,7 +379,7 @@ Finished in .001441 seconds ``` Disabling the context(s). -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -410,7 +409,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -425,7 +424,7 @@ Finished in .005079 seconds ``` Disabling individual test(s). -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -446,7 +445,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -463,7 +462,7 @@ Finished in .005868 seconds There are two possible ways to use the `--%beforeall` annotation. As a procedure level annotation: -```sql +```sql linenums="1" --%suite(Some test suite) --%beforeall @@ -475,7 +474,7 @@ procedure some_test; Marks annotated procedure to be executed before all test procedures in a suite. As a package level annotation (not associated with any procedure). -```sql +```sql linenums="1" --%suite(Some test suite) --%beforeall(to_be_executed_before_all, other_package.some_setup) @@ -492,6 +491,7 @@ Indicates that the procedure(s) mentioned as the annotation parameter are to be If `--%beforeall` raises an exception, suite content cannot be safely executed as the setup was not executed successfully for the suite. If `--%beforeall` raises an exception the following will happen: + - the `--%beforeall` procedures that follow the failed one, **will not be executed** - all `--%test` procedures and their `--%beforeeach`, `--%aftereach`, `--%beforetest` and `--%aftertest` procedures within suite package **will not be executed** - all `--%test` procedures **will be marked as failed** @@ -502,7 +502,7 @@ When multiple `--%beforeall` procedures are defined in a suite package, all of t For multiple `--%beforeall` procedures order of execution is defined by annotation position in the package specification. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -530,9 +530,10 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` + ``` Tests for a package --- SETUP_STUFF invoked --- @@ -546,7 +547,7 @@ Finished in .012292 seconds In the below example a combination pacakge and procedure level `--%beforeall` annotations is used. The order of execution of the beforeall procedures is determined by the annotation position in package. All of the `--%beforeall` procedures get invoked before any test is executed in a suite. - ```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -595,12 +596,13 @@ All of the `--%beforeall` procedures get invoked before any test is executed in procedure other_test is begin null; end; end; / - ``` +``` - ```sql +```sql linenums="1" exec ut.run('test_package'); - ``` - ``` +``` + +``` Tests for a package --- INITIAL_SETUP invoked --- --- ANOTHER_SETUP invoked --- @@ -611,11 +613,11 @@ Tests for a package Finished in .018944 seconds 2 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) - ``` +``` When multiple `--%beforeall` annotations are specified for a procedure, the first annotation will be used and a warning message will appear indicating duplicate annotation. When procedure is annotated as both `--%beforeall` and `--%test`, the procedure will become a test and a warning message will appear indicating invalid annotation combination. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -646,10 +648,11 @@ When procedure is annotated as both `--%beforeall` and `--%test`, the procedure / ``` - ```sql +```sql linenums="1" exec ut.run('test_package'); - ``` - ``` +``` + +``` Tests for a package --- INITIAL_SETUP invoked --- Description of tested behavior [.003 sec] @@ -667,7 +670,7 @@ Warnings: Finished in .012158 seconds 2 tests, 0 failed, 0 errored, 0 disabled, 2 warning(s) - ``` +``` ### Afterall @@ -675,7 +678,7 @@ Finished in .012158 seconds There are two possible ways to use the `--%afterall` annotation. As a procedure level annotation: -```sql +```sql linenums="1" --%suite(Some test suite) --%afterall @@ -687,7 +690,7 @@ procedure some_test; Marks annotated procedure to be executed after all test procedures in a suite. As a package level annotation (not associated with any procedure). -```sql +```sql linenums="1" --%suite(Some test suite) --%afterall(to_be_executed_after_all, other_package.some_cleanup) @@ -696,11 +699,12 @@ As a package level annotation (not associated with any procedure). procedure some_test; procedure to_be_executed_after_all; - ``` + Indicates that the procedure(s) mentioned as the annotation parameter are to be executed after all test procedures in a suite. If `--%afterall` raises an exception the following will happen: + - a warning will be raised, indicating that `--%afterall` procedure has failed - execution will continue uninterrupted for rest of the suite @@ -713,7 +717,7 @@ For multiple `--%afterall` procedures order of execution is defined by annotatio All rules defined for `--%beforeall` also apply for `--%afterall` annotation. See [beforeall](#Beforeall) for more details. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -741,9 +745,10 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` + ``` Tests for a package Description of tested behavior [.003 sec] @@ -762,7 +767,7 @@ That means that the procedure will be executed as many times as there are test i There are two possible ways to use the `--%beforeeach` annotation. As a procedure level annotation: -```sql +```sql linenums="1" --%suite(Some test suite) --%beforeeach @@ -774,7 +779,7 @@ procedure some_test; Marks annotated procedure to be executed before each test procedures in a suite. As a package level annotation (not associated with any procedure). -```sql +```sql linenums="1" --%suite(Some test suite) --%beforeeach(to_be_executed_before_each, other_package.some_setup) @@ -783,14 +788,15 @@ As a package level annotation (not associated with any procedure). procedure some_test; procedure to_be_executed_before_each; - ``` + Indicates that the procedure(s) mentioned as the annotation parameter are to be executed before each test procedure in a suite. If a test is marked as disabled the `--%beforeeach` procedure is not invoked for that test. If `--%beforeeach` raises an unhandled exception the following will happen: + - the following `--%beforeeach` as well as all `--%beforetest` for that test **will not be executed** - the test will be marked as errored and exception stack trace will be captured and reported - the `--%aftertest`, `--%aftereach` procedures **will be executed** for the errored test @@ -803,7 +809,7 @@ When multiple `--%beforeeach` procedures are defined in a suite, all of them wil For multiple `--%beforeeach` procedures order of execution is defined by annotation position in the package specification. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -844,9 +850,10 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` + ``` Tests for a package ---SETUP_STUFF invoked --- @@ -873,7 +880,7 @@ That means that the procedure will be executed as many times as there are test i There are two possible ways to use the `--%aftereach` annotation. As a procedure level annotation: -```sql +```sql linenums="1" --%suite(Some test suite) --%aftereach @@ -885,7 +892,7 @@ procedure some_test; Marks annotated procedure to be executed after each test procedures in a suite. As a package level annotation (not associated with any procedure). -```sql +```sql linenums="1" --%suite(Some test suite) --%aftereach(to_be_executed_after_each, other_package.some_setup) @@ -894,13 +901,14 @@ As a package level annotation (not associated with any procedure). procedure some_test; procedure to_be_executed_after_each; - ``` + Indicates that the procedure(s) mentioned as the annotation parameter are to be executed after each test procedure in a suite. If a test is marked as disabled the `--%aftereach` procedure is not invoked for that test. If `--%aftereach` raises an unhandled exception the following will happen: + - the test will be marked as errored and exception stack trace will be captured and reported - the `--%aftertest`, `--%aftereach` procedures **will be executed** for the errored test - the `--%afterall` procedures **will be executed** @@ -912,7 +920,7 @@ For multiple `--%aftereach` procedures order of execution is defined by the anno As a rule, the `--%aftereach` gets executed even if the associated `--%beforeeach`, `--%beforetest`, `--%test` or other `--%aftereach` procedures have raised unhandled exceptions. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -952,7 +960,7 @@ create or replace package body test_package as end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -974,6 +982,7 @@ See [beforeall](#Beforeall) for more examples. ### Beforetest Indicates specific setup procedure(s) to be executed for a test. The procedure(s) can be located either: + - within current package (package name is optional) - within another package @@ -984,6 +993,7 @@ The `--%beforetest` procedures are executed after invoking all `--%beforeeach` f If a test is marked as disabled the `--%beforetest` procedures are not invoked for that test. If `--%beforetest` raises an unhandled exception the following will happen: + - the following `--%beforetest` for that test **will not be executed** - the test will be marked as errored and exception stack trace will be captured and reported - the `--%aftertest`, `--%aftereach` procedures **will be executed** for the errored test @@ -993,12 +1003,13 @@ If `--%beforetest` raises an unhandled exception the following will happen: When multiple `--%beforetest` procedures are defined for a test, all of them will be executed before invoking the test. The order of execution for `--%beforetest` procedures is defined by: + - position of procedure on the list within single annotation - annotation position As a rule, the `--%beforetest` execution gets aborted if preceding `--%beforeeach` or `--%beforetest` failed. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -1040,7 +1051,7 @@ create or replace package body test_package as end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -1062,6 +1073,7 @@ Finished in .015185 seconds ### Aftertest Indicates specific cleanup procedure(s) to be executed for a test. The procedure(s) can be located either: + - within current package (package name is optional) - within another package @@ -1070,6 +1082,7 @@ The annotation need to be placed alongside `--%test` annotation. If a test is marked as disabled the `--%aftertest` procedures are not invoked for that test. If `--%aftertest` raises an unhandled exception the following will happen: + - the test will be marked as errored and exception stack trace will be captured and reported - the following `--%aftertest` and all `--%aftereach` procedures **will be executed** for the errored test - the `--%afterall` procedures **will be executed** @@ -1078,12 +1091,13 @@ If `--%aftertest` raises an unhandled exception the following will happen: When multiple `--%aftertest` procedures are defined for a test, all of them will be executed after invoking the test. The order of execution for `--%aftertest` procedures is defined by: + - position of procedure on the list within single annotation - annotation position As a rule, the `--%aftertest` gets executed even if the associated `--%beforeeach`, `--%beforetest`, `--%test` or other `--%aftertest` procedures have raised unhandled exceptions. -```sql +```sql linenums="1" create or replace package test_package as --%suite(Tests for a package) @@ -1125,7 +1139,7 @@ create or replace package body test_package as end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -1160,6 +1174,7 @@ Contexts allow for creating sub-suites within a suite package and they allow for In essence, context behaves like a suite within a suite. Context have following characteristics: + - context starts with the `--%context` annotation and ends with `--%endcontext`. Everything placed between those two annotations belongs to that context - can have a description provided as parameter for example `--%context(Some interesting stuff)`. - can have a name provided with `--%name` annotation. This is different than with `suite` and `test` annotations, where name is taken from `package/procedure` name. @@ -1177,7 +1192,7 @@ Context have following characteristics: The below example illustrates usage of `--%context` for separating tests for individual procedures of package. Sample tables and code -```sql +```sql linenums="1" create table rooms ( room_key number primary key, name varchar2(100) not null @@ -1234,11 +1249,12 @@ end; ``` Below test suite defines: + - `--%beforeall` outside of context, that will be executed before all tests - `--%context(remove_rooms_by_name)` to group tests related to `remove_rooms_by_name` functionality - `--%context(add_rooms_content)` to group tests related to `add_rooms_content` functionality -```sql +```sql linenums="1" create or replace package test_rooms_management is gc_null_value_exception constant integer := -1400; @@ -1357,8 +1373,8 @@ end; / ``` -When te tests are executed -```sql +When the tests are executed +```sql linenums="1" exec ut.run('test_rooms_management'); ``` The following report is displayed @@ -1380,7 +1396,7 @@ Finished in .035261 seconds Example of nested contexts test suite specification. *Source - [slide 145](https://www.slideshare.net/Kevlin/structure-and-interpretation-of-test-cases/145?src=clipshare) of Structure and Interpretation of Test Cases by Kevlin Henney* -```sql +```sql linenums="1" create or replace package queue_spec as --%suite(Queue specification) @@ -1470,7 +1486,7 @@ The `--%name` can be useful when you would like to run only a specific context o Consider the below example. -```sql +```sql linenums="1" create or replace package queue_spec as --%suite(Queue specification) @@ -1503,22 +1519,22 @@ end; In the above code, suitepaths, context names and context descriptions will be as follows. -| suitepath | description | name | -|-----------|------------|------| -| queue_spec | Queue specification | queue_spec | -| queue_spec.context_#1 | A new queue | context_#1 | -| queue_spec.context_#2 | An empty queue | context_#2 | -| queue_spec.context_#3 | A non empty queue | context_#3 | -| queue_spec.context_#3.context_#1 | that is not full | context_#1 | -| queue_spec.context_#3.context_#2 | that is full | context_#2 | +| suitepath | description | name | +|----------------------------------|----------------------|------------| +| queue_spec | Queue specification | queue_spec | +| queue_spec.context_#1 | A new queue | context_#1 | +| queue_spec.context_#2 | An empty queue | context_#2 | +| queue_spec.context_#3 | A non empty queue | context_#3 | +| queue_spec.context_#3.context_#1 | that is not full | context_#1 | +| queue_spec.context_#3.context_#2 | that is full | context_#2 | In order to run only the tests for the context `A non empty queue that is not full` you will need to call utPLSQL as below: -```sql +```sql linenums="1" exec ut.run(':queue_spec.context_#3.context_#1'); ``` You can use `--%name` annotation to explicitly name contexts on suitepath. -```sql +```sql linenums="1" create or replace package queue_spec as --%suite(Queue specification) @@ -1556,23 +1572,25 @@ end; In the above code, suitepaths, context names and context descriptions will be as follows. -| suitepath | description | name | -|-----------|------------|------| -| queue_spec | Queue specification | queue_spec | -| queue_spec.a_new_queue | A new queue | a_new_queue | -| queue_spec.an_empty_queue | An empty queue | an_empty_queue | -| queue_spec.a_non_empty_queue | A non empty queue | a_non_empty_queue | -| queue_spec.a_non_empty_queue.that_is_not_full | that is not full | that_is_not_full | -| queue_spec.a_non_empty_queue.that_is_full | that is full | that_is_full | +| suitepath | description | name | +|-----------------------------------------------|-----------------------|-------------------| +| queue_spec | Queue specification | queue_spec | +| queue_spec.a_new_queue | A new queue | a_new_queue | +| queue_spec.an_empty_queue | An empty queue | an_empty_queue | +| queue_spec.a_non_empty_queue | A non empty queue | a_non_empty_queue | +| queue_spec.a_non_empty_queue.that_is_not_full | that is not full | that_is_not_full | +| queue_spec.a_non_empty_queue.that_is_full | that is full | that_is_full | The `--%name` annotation is only relevant for: + - running subsets of tests by given context suitepath - some of test reports, like `ut_junit_reporter` that use suitepath or test-suite element names (not descriptions) for reporting #### Name naming convention The value of `--%name` annotation must follow the following naming rules: + - cannot contain spaces - cannot contain a `.` (full stop/dot) - is case-insensitive @@ -1585,106 +1603,38 @@ It allows for grouping of tests / suites using various categorization and place e.g. -```sql +```sql linenums="1" --%tags(batch,daily,csv) ``` or -```sql +```sql linenums="1" --%tags(online,json) --%tags(api) ``` Tags are defined as a comma separated list within the `--%tags` annotation. -When executing a test run with tag filter applied, the framework will find all tests associated with the given tags and execute them. -The framework applies `OR` logic to all specified tags so any test / suite that matches at least one tag will be included in the test run. - -When a suite/context is tagged, all of its children will automatically inherit the tag and get executed along with the parent. Parent suite tests are not executed, but a suitepath hierarchy is kept. - - -Sample test suite package with tags. -```sql -create or replace package ut_sample_test is - - --%suite(Sample Test Suite) - --%tags(api) - - --%test(Compare Ref Cursors) - --%tags(complex,fast) - procedure ut_refcursors1; - - --%test(Run equality test) - --%tags(simple,fast) - procedure ut_test; - -end ut_sample_test; -/ - -create or replace package body ut_sample_test is - - procedure ut_refcursors1 is - v_actual sys_refcursor; - v_expected sys_refcursor; - begin - open v_expected for select 1 as test from dual; - open v_actual for select 2 as test from dual; - - ut.expect(v_actual).to_equal(v_expected); - end; - - procedure ut_test is - begin - ut.expect(1).to_equal(0); - end; - -end ut_sample_test; -/ -``` - -Execution of the test is done by using the parameter `a_tags` - -```sql -select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'api')); -``` -The above call will execute all tests from `ut_sample_test` package as the whole suite is tagged with `api` - -```sql -select * from table(ut.run(a_tags => 'complex')); -``` -The above call will execute only the `ut_sample_test.ut_refcursors1` test, as only the test `ut_refcursors1` is tagged with `complex` - -```sql -select * from table(ut.run(a_tags => 'fast')); -``` -The above call will execute both `ut_sample_test.ut_refcursors1` and `ut_sample_test.ut_test` tests, as both tests are tagged with `fast` +When a suite/context is tagged, all of its children will automatically inherit the tag and get executed along with the parent, unless they are excluded explicitly at runtime with a negated tag expression. +See [running unit tests](running-unit-tests.md) for more information on using tags to filter test suites that are to be executed. #### Tag naming convention Tags must follow the below naming convention: - tag is case sensitive -- tag can contain special characters like `$#/\?-!` etc. -- tag cannot be an empty string +- tag must not contain any of the following reserved characters: + - comma (,) + - left or right parenthesis ((, )) + - ampersand (&) + - vertical bar (|) + - exclamation point (!) +- tag cannot be null or blank - tag cannot start with a dash, e.g. `-some-stuff` is **not** a valid tag - tag cannot contain spaces, e.g. `test of batch`. To create a multi-word tag use underscores or dashes, e.g. `test_of_batch`, `test-of-batch` - leading and trailing spaces are ignored in tag name, e.g. `--%tags( tag1 , tag2 )` becomes `tag1` and `tag2` tag names - - -#### Excluding tests/suites by tags - -It is possible to exclude parts of test suites with tags. -In order to do so, prefix the tag name to exclude with a `-` (dash) sign when invoking the test run. - -Examples (based on above sample test suite) - -```sql -select * from table(ut.run(a_tags => 'api,fast,-complex')); -``` -The above call will execute all suites/contexts/tests that are marked with any of tags `api` or `fast` except those suites/contexts/tests that are marked as `complex`. -Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed. - +- tag cannot be one of two reserved words: `none` and `any`. `none` and `any` as a tag will be treated as no tag ### Suitepath @@ -1707,7 +1657,7 @@ If you want to create tests for your application it is recommended to structure The `--%suitepath` annotation is used for such grouping. Even though test packages are defined in a flat structure the `--%suitepath` is used by the framework to form them into a hierarchical structure. Your payments recognition test package might look like: -```sql +```sql linenums="1" create or replace package test_payment_recognition as --%suite(Payment recognition tests) @@ -1726,7 +1676,7 @@ end test_payment_recognition; ``` And payments set off test package: -```sql +```sql linenums="1" create or replace package test_payment_set_off as --%suite(Payment set off tests) @@ -1745,7 +1695,7 @@ When you execute tests for your application, the framework constructs a test sui The test report indicates which expectation has failed on the payments module. The payments recognition submodule is causing the failure as `recognize_by_num` has not met the expectations of the test. Grouping tests into modules and submodules using the `--%suitepath` annotation allows you to logically organize your project's flat structure of packages into functional groups. An additional advantage of such grouping is the fact that every element level of the grouping can be an actual unit test package containing a common module level setup for all of the submodules. So in addition to the packages mentioned above you could have the following package. -```sql +```sql linenums="1" create or replace package payments as --%suite(Payments) @@ -1760,6 +1710,7 @@ end payments; ``` When executing tests, `path` for executing tests can be provided in three ways: + * schema - execute all tests in the schema * [schema]:suite1[.suite2][.suite3]...[.procedure] - execute all tests by `suitepath` in all suites on path suite1[.suite2][.suite3]...[.procedure]. If schema is not provided, then the current schema is used. Example: `:all.rooms_tests` * [schema.]package[.procedure] - execute all tests in the specified test package. The whole hierarchy of suites in the schema is built before all before/after hooks or part suites for the provided suite package are executed as well. Example: `tests.test_contact.test_last_name_validator` or simply `test_contact.test_last_name_validator` if `tests` is the current schema. @@ -1800,6 +1751,7 @@ Keep in mind that when your test runs as autonomous transaction it will not see ### Throws The `--%throws` annotation allows you to specify a list of exceptions as one of: + - number literals - example `--%throws(-20134)` - variables of type exception defined in a package specification - example `--%throws(exc_pkg.c_exception_No_variable)` - variables of type number defined in a package specification - example `--%throws(exc_pkg.c_some_exception)` @@ -1816,9 +1768,9 @@ The framework will raise a warning, when `--%throws` annotation has invalid argu Annotation `--%throws(7894562, operaqk, -=1, -20496, pow74d, posdfk3)` will be interpreted as `--%throws(-20496)`. Please note that `NO_DATA_FOUND` exception is a special case in Oracle. To capture it use `NO_DATA_FOUND` named exception or `-1403` exception No. -​ + Example: -```sql +```sql linenums="1" create or replace package exc_pkg is c_e_option1 constant number := -20200; c_e_option2 constant varchar2(10) := '-20201'; @@ -1981,7 +1933,7 @@ Finished in .025784 seconds ## Order of execution -```sql +```sql linenums="1" create or replace package test_employee_pkg is --%suite(Employee management) @@ -2103,46 +2055,50 @@ When processing the test suite `test_employee_pkg` defined in [Example of annota rollback to savepoint 'before-suite' ``` -**Note** ->utPLSQL does not guarantee ordering of tests in suite. On contrary utPLSQL might give random order of tests/contexts in suite. -> ->Order of execution within multiple occurrences of `before`/`after` procedures is determined by the order of annotations in specific block (context/suite) of package specification. +!!! note + utPLSQL does not guarantee ordering of tests in suite. On contrary utPLSQL might give random order of tests/contexts in suite.
+ Order of execution within multiple occurrences of `before`/`after` procedures is determined by the order of annotations in specific block (context/suite) of package specification. ## sys_context It is possible to access information about currently running suite. The information is available by calling `sys_context( 'UT3_INFO', attribute )`. -It can be accessed from any procecure invoked as part of utPLSQL test execution. +It can be accessed from any procedure invoked as part of utPLSQL test execution. -**Note:** -> Context name is derived from schema name where utPLSQL is installed. -> The context name in below examples represents the default install schema -> `UT3` -> If you install utPLSQL into another schema the context name will be different. -> For example if utPLSQL is installed into `HR` schema, the context name will be `HR_INFO` +!!! note + Context name is derived from schema name where utPLSQL is installed.
+ The context name in below examples represents the default install schema -> `UT3`
+ If you install utPLSQL into another schema the context name will be different.
+ For example if utPLSQL is installed into `HR` schema, the context name will be `HR_INFO` Following attributes are populated: -- For entire duration of the test-run: - - `sys_context( 'UT3_INFO', 'COVERAGE_RUN_ID' );` - Value of COVERAGE_RUN_ID used by utPLSQL internally for coverage gathering - - `sys_context( 'UT3_INFO', 'RUN_PATHS' );` - list of suitepaths / suitenames used as input parameters for call to `ut.run(...)` or `ut_runner.run(...)` - - `sys_context( 'UT3_INFO', 'SUITE_DESCRIPTION' );` - the description of test suite that is currently being executed - - `sys_context( 'UT3_INFO', 'SUITE_PACKAGE' );` - the owner and name of test suite package that is currently being executed - - `sys_context( 'UT3_INFO', 'SUITE_PATH' );` - the suitepath for the test suite package that is currently being executed - - `sys_context( 'UT3_INFO', 'SUITE_START_TIME' );` - the execution start timestamp of test suite package that is currently being executed - - `sys_context( 'UT3_INFO', 'CURRENT_EXECUTABLE_NAME' );` - the owner.package.procedure of currently running test suite executable - - `sys_context( 'UT3_INFO', 'CURRENT_EXECUTABLE_TYPE' );` - the type of currently running test suite executable (one of: `beforeall`, `beforeeach`, `beforetest`, `test`, `aftertest`, `aftereach`, `afterall` - -- When running in suite context - - `sys_context( 'UT3_INFO', 'CONTEXT_DESCRIPTION' );` - the description of test suite context that is currently being executed - - `sys_context( 'UT3_INFO', 'CONTEXT_NAME' );` - the name of test suite context that is currently being executed - - `sys_context( 'UT3_INFO', 'CONTEXT_PATH' );` - the suitepath for the currently executed test suite context - - `sys_context( 'UT3_INFO', 'CONTEXT_START_TIME' );` - the execution start timestamp for the currently executed test suite context -- When running a suite executable procedure that is a `test` or `beforeeach`, `aftereach`, `beforetest`, `aftertest` - - `sys_context( 'UT3_INFO', 'TEST_DESCRIPTION' );` - the description of test for which the current executable is being invoked - - `sys_context( 'UT3_INFO', 'TEST_NAME' );` - the name of test for which the current executable is being invoked - - `sys_context( 'UT3_INFO', 'TEST_START_TIME' );` - the execution start timestamp of test that is currently being executed (the time when first `beforeeach`/`beforetest` was called for that test) + +| Name | Scope | Description | +|-------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| COVERAGE_RUN_ID | run | Value of COVERAGE_RUN_ID used by utPLSQL internally for coverage gathering | +| RUN_PATHS | run | list of suitepaths / suitenames used as input parameters for call to `ut.run(...)` or `ut_runner.run(...)` | +| SUITE_DESCRIPTION | run | the description of test suite that is currently being executed | +| SUITE_PACKAGE | run | the owner and name of test suite package that is currently being executed | +| SUITE_PATH | run | the suitepath for the test suite package that is currently being executed | +| SUITE_START_TIME | run | the execution start timestamp of test suite package that is currently being executed | +| CURRENT_EXECUTABLE_NAME | run | the owner.package.procedure of currently running test suite executable | +| CURRENT_EXECUTABLE_TYPE | run | the type of currently running test suite executable (one of: `beforeall`, `beforeeach`, `beforetest`, `test`, `aftertest`, `aftereach`, `afterall` | + | CONTEXT_DESCRIPTION | suite context | the description of test suite context that is currently being executed | + | CONTEXT_NAME | suite context | the name of test suite context that is currently being executed | + | CONTEXT_PATH | suite context | the suitepath for the currently executed test suite context | + | CONTEXT_START_TIME | suite context | the execution start timestamp for the currently executed test suite context | +| TEST_DESCRIPTION | test* | the description of test for which the current executable is being invoked | +| TEST_NAME | test* | the name of test for which the current executable is being invoked | +| TEST_START_TIME | test* | the execution start timestamp of test that is currently being executed (the time when first `beforeeach`/`beforetest` was called for that test) | + +!!! note "Scopes" + - run - context information is available in any element of test run
+ - suite context - context information is available in any element nested within a suite context
+ - test* - context information is available when executing following procedure types: test, beforetest, aftertest, beforeeach or aftereach + Example: -```sql +```sql linenums="1" create or replace procedure which_procecure_called_me is begin dbms_output.put_line( @@ -2201,7 +2157,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_call'); ``` @@ -2234,14 +2190,14 @@ If you are in a situation where your database is controlled via CI/CD server and To build the annotation cache without actually invoking any tests, call `ut_runner.rebuild_annotation_cache(a_object_owner)` for every unit test owner for which you want to have the annotation cache prebuilt. Example: -```sql +```sql linenums="1" exec ut_runner.rebuild_annotation_cache('HR'); ``` To purge the annotation cache call `ut_runner.purge_cache(a_object_owner, a_object_type)`. Both parameters are optional and if not provided, all owners/object_types will be purged. Example: -```sql +```sql linenums="1" exec ut_runner.purge_cache('HR', 'PACKAGE'); ``` diff --git a/docs/userguide/best-practices.md b/docs/userguide/best-practices.md index 85a4351ce..6becefc5c 100644 --- a/docs/userguide/best-practices.md +++ b/docs/userguide/best-practices.md @@ -1,6 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -# Best Practices +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) The following are best practices we at utPLSQL have learned about PL/SQL and Unit Testing. @@ -17,9 +15,9 @@ The following are best practices we at utPLSQL have learned about PL/SQL and Uni - Tests should not mimic / duplicate the logic of tested code - Tests should contain zero logic (or as close to zero as possible) - The 3A rule: - - Arrange (setup inputs/data/environment for the tested code) - - Act (execute code under test) - - Assert (validate the outcomes of the execution) + - Arrange (setup inputs/data/environment for the tested code) + - Act (execute code under test) + - Assert (validate the outcomes of the execution) - Each tested procedure/function/trigger (code block) should have more than one test - Each test should check only one behavior (one requirement) of the code block under test - Tests should be maintained as thoroughly as production code diff --git a/docs/userguide/coverage.md b/docs/userguide/coverage.md index b66b5dff7..c9015b438 100644 --- a/docs/userguide/coverage.md +++ b/docs/userguide/coverage.md @@ -1,20 +1,26 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) -# Coverage utPLSQL comes with a built-in coverage reporting engine. The code coverage reporting uses package DBMS_PROFILER (and DBMS_PLSQL_CODE_COVERAGE on Oracle database version 12.2 and above) provided with Oracle database. Code coverage is gathered for the following source types: + * package bodies * type bodies * triggers * procedures * functions -**Note** +!!! note -> The package and type specifications are excluded from code coverage analysis. This limitation is introduced to avoid false-negatives. Typically package specifications contain no executable code. The only exception is initialization of global constants and variables in package specification. Since most package specifications are not executable at all, there is no information available on the number of lines covered and those would be reported as 0% covered, which is not desirable. + The package and type specifications are excluded from code coverage analysis. + This limitation is introduced to avoid false-negatives. + Typically package specifications contain no executable code. + The only exception is initialization of global constants and variables in package specification. + Since most package specifications are not executable at all, there is no information available + on the number of lines covered and those would be reported as 0% covered, which is not desirable. To obtain information about code coverage for unit tests, run utPLSQL with one of built-in code coverage reporters. The following code coverage reporters are supplied with utPLSQL: + * `ut_coverage_html_reporter` - generates a HTML coverage report providing summary and detailed information on code coverage. The HTML reporter is based on the open-source [simplecov-html](https://github.com/colszowka/simplecov-html) reporter for Ruby. It includes source code of the code that was covered (if the code is accessible for test user) * `ut_coveralls_reporter` - generates a [Coveralls compatible JSON](https://coveralls.zendesk.com/hc/en-us/articles/201774865-API-Introduction) coverage report providing detailed information on code coverage with line numbers. This coverage report is designed to be consumed by cloud services like [Coveralls](https://coveralls.io) * `ut_coverage_sonar_reporter` - generates a [Sonar Compatible XML](https://docs.sonarqube.org/latest/analysis/generic-test/) coverage report providing detailed information on code coverage with line numbers. This coverage report is designed to be consumed by services like [SonarQube](https://www.sonarqube.org/) and [SonarCloud](https://about.sonarcloud.io/) @@ -23,6 +29,7 @@ The following code coverage reporters are supplied with utPLSQL: ## Security model utPLSQL code coverage uses DBMS_PROFILER to gather information about the execution of code under test and therefore follows the [DBMS_PROFILER's Security Model](https://docs.oracle.com/database/121/ARPLS/d_profil.htm#ARPLS67465). In order to be able to gather coverage information, the user executing unit tests needs to be either: + * The owner of the code that is being tested * Have the following privileges to be able to gather coverage on code owned by other users: * `create any procedure` system privilege @@ -37,7 +44,7 @@ Using the code coverage functionality is as easy as using any other [reporter](r All you need to do, is pass the constructor of the reporter to the `ut.run` procedure call. Example: -```sql +```sql linenums="1" set serveroutput on begin ut.run(ut_coverage_html_reporter()); @@ -56,7 +63,7 @@ The report allow you to navigate to each source file and inspect line by line co ![Coverage Details page](../images/coverage_html_details.png) -#### Oracle 12.2 extended coverage with profiler and block coverage +### Oracle 12.2 extended coverage with profiler and block coverage Using data collected from profiler and block coverage running parallel we are able to enrich information about coverage. For every line recorded by the profiler if we have a partially covered same line in block coverage we will display that information presenting line as partially covered, displaying number of block and how many blocks have been covered in that line.The feature will be automatically enabled in the Oracle database version 12.2 and higher, for older versions current profiler will be used. @@ -87,8 +94,8 @@ The parameters used to execute tests determine if utPLSQL will be using one appr If parameter `a_source_file_mappings` or `a_source_files` is provided, then coverage is gathered on project files provided, otherwise coverage is gathered on schemas. -**Note** -> Regardless of the options provided, all unit test packages are excluded from the coverage report. Coverage reports provide information only about the **tested** code. +!!! note + Regardless of the options provided, all unit test packages are excluded from the coverage report. Coverage reports provide information only about the **tested** code. The default behavior of coverage reporting can be altered using invocation parameters. @@ -97,17 +104,16 @@ The default behavior of coverage reporting can be altered using invocation param To gather coverage for all objects in the **current schema** execute tests with coverage report as argument. This is the default reporting option and therefore additional coverage options don't need to be provided. -```sql +```sql linenums="1" exec ut.run(ut_coverage_html_reporter()); ``` -**Note** +!!! note -> When no filters are used, the size of the coverage report will depend two factors: -> - the type of report (does the report include source code or not) -> - the amount of source code in the database schema -> ->Keep in mind that for schemas containing a lot of code, it can take quite some time to produce the coverage report. + When no filters are used, the size of the coverage report will depend two factors:
+ - the type of report (does the report include source code or not)
+ - the amount of source code in the database schema
+ Keep in mind that for schemas containing a lot of code, it can take quite some time to produce the coverage report. #### Setting coverage schema(s) @@ -115,7 +121,7 @@ By default, coverage is gathered on the schema(s) derived from suite paths provi This is a valid approach as long as your test packages and tested code share the same schema. So when you run: -```sql +```sql linenums="1" exec ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter()); ``` Coverage will be gathered on both `user_1` and `user_2` objects. @@ -124,13 +130,18 @@ If your tests live in a different schema from the tested code you may override t In the example below, coverage will still be gathered for `user_1` and `user_2` objects, even thought we run the tests located in schema `unit_test_schema` -```sql -exec ut.run('unit_test_schema', ut_coverage_html_reporter(), a_coverage_schemes => ut_varchar2_list('user_1','user_2') ); +```sql linenums="1" +begin + ut.run('unit_test_schema', ut_coverage_html_reporter(), + a_coverage_schemes => ut_varchar2_list('user_1','user_2') + ); +end; ``` #### Filtering objects in coverage reports Multiple parameters can be used to define the scope of coverage report. + - `a_source_file_mappings ( ut_file_mappings )` - map of filenames to database objects. It is used for file-based coverage - see below. - `a_include_schema_expr ( varchar(4000) )` - string of regex expression of schemas to be included in the coverage report. Case-insensitive. - `a_include_object_expr ( varchar(4000) )` - string of regex expression of objects ( without schema name ) to be included in the coverage report. Case-insensitive. @@ -142,21 +153,23 @@ Multiple parameters can be used to define the scope of coverage report. You may specify both _include_ and _exclude_ options to gain more control over what needs to be included / excluded from the coverage report. -**Important notes** -The order of priority is for evaluation of include/exclude filter parameters is as follows. -- if `a_source_file_mappings` is defined then all include/exclude parameters are ignored (see section below for usage of `a_source_file_mappings` parameter ) -- else if `a_include_schema_expr` or `a_include_object_expr` parameter is specified then parameters `a_coverage_schemes` and `a_include_objects` are ignored -- else if `a_include_objects` is specified then the coverage is gathered only on specified database objects. - - if `a_coverage_schemes` is specified then those schemas are used for objects in `a_include_objects` without schema name - - if `a_coverage_schemes` is not specified then schema from paths (`a_paths`) parameter are used for objects in `a_include_objects` without schema name -- else if, only the `a_coverage_schemes` is specified then the coverage is gathered only on specified database schemas -- else if no coverage specific parameters are provided coverage is gathered on all schemas specified in paths passed to run procedure -- else if no paths were specified, the coverage is gathered on current schema of the session running the tests +!!! warning "Important note" + + The order of priority is for evaluation of include/exclude filter parameters is as follows.
+ - if `a_source_file_mappings` is defined then all include/exclude parameters are ignored (see section below for usage of `a_source_file_mappings` parameter )
+ - else if `a_include_schema_expr` or `a_include_object_expr` parameter is specified then parameters `a_coverage_schemes` and `a_include_objects` are ignored
+ - else if `a_include_objects` is specified then the coverage is gathered only on specified database objects.
+ - if `a_coverage_schemes` is specified then those schemas are used for objects in `a_include_objects` without schema name
+ - if `a_coverage_schemes` is not specified then schema from paths (`a_paths`) parameter are used for objects in `a_include_objects` without schema name
+ - else if, only the `a_coverage_schemes` is specified then the coverage is gathered only on specified database schemas
+ - else if no coverage specific parameters are provided coverage is gathered on all schemas specified in paths passed to run procedure
+ - else if no paths were specified, the coverage is gathered on current schema of the session running the tests + The exclude parameters are not mutually-exclusive and can be mixed together. All of exclude parameters are always applied. Example: Limiting coverage by schema regex. -```sql +```sql linenums="1" begin ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_include_schema_expr => '^ut3_develop' @@ -166,7 +179,7 @@ end; Will result in showing coverage for all schemas that match regular expression `^ut3_develop` Example: Limiting coverage by schema regex with parameter `a_include_objects` ignored. -```sql +```sql linenums="1" begin ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_include_schema_expr => '^ut3_develop', a_include_objects => ut_varchar2_list( 'ut3_tester_helper.regex_dummy_cov' ) @@ -176,7 +189,7 @@ end; Will result in showing coverage for all schemas that match regular expression `^ut3_develop`. Example: Limiting coverage by object regex. -```sql +```sql linenums="1" begin ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_include_object_expr => 'regex123' @@ -186,7 +199,7 @@ end; Will result in showing coverage for all objects that name match regular expression `regex123`. Example: Limiting coverage by object regex with parameter `a_include_objects` ignored. -```sql +```sql linenums="1" begin ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_include_object_expr => 'utl', a_include_objects => ut_varchar2_list( 'user_2.utils_package' ) @@ -196,7 +209,7 @@ end; Will result in showing coverage for all objects that name match regular expression `utl`. Example: Limiting coverage by excluding schema with regex. -```sql +```sql linenums="1" begin ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_exclude_schema_expr => 'er_1$' @@ -206,7 +219,7 @@ end; Will result in showing coverage for objects in all schema except schemas that are matching regular expression `er_1$` Example: Limiting coverage by excluding schema with regex and excluding specific object. -```sql +```sql linenums="1" begin ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_exclude_schema_expr => 'er_1$', a_exclude_objects => ut_varchar2_list( 'user_2.utils_package' ) @@ -217,7 +230,7 @@ Will result in showing coverage for objects in all schemas except schemas that a Will also exclude object `user_2.utils_package` from coverage report Example: Limiting coverage by excluding objects with regex. -```sql +```sql linenums="1" begin ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_exclude_object_expr => 'utl' @@ -227,7 +240,7 @@ end; Will result in showing coverage for all objects that name is not matching regular expression `utl`. Example: Limiting coverage by excluding objects with regex with parameter `a_exclude_objects` ignored. -```sql +```sql linenums="1" begin ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_exclude_object_expr => 'utl', a_exclude_objects => ut_varchar2_list( 'user_2.utils_package' ) @@ -239,14 +252,18 @@ Will also exclude object `user_2.utils_package` from coverage report Example: Limiting coverage by object name, for tested code located in the same schema as the unit tests. -```sql -exec ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), a_include_objects=>ut_varchar2_list('award_bonus')); +```sql linenums="1" +begin + ut.run(ut_varchar2_list('user_1','user_2'), ut_coverage_html_reporter(), + a_include_objects=>ut_varchar2_list('award_bonus') + ); +end; ``` Executes all tests in schemas: `user_1` and `user_2`. Coverage will only be reported on objects `user_1.award_bonus`, `user_2.award_bonus` Example: Limiting coverage by object name, for tested code located in different schemas than the unit tests. -```sql +```sql linenums="1" begin ut.run( 'unit_test_schema', ut_coverage_html_reporter(), @@ -259,7 +276,7 @@ Executes all tests in schema `unit_test_schema`. Coverage will only be reported Objects that do not exist in the database but were specified in `a_include_objects` will be ignored. Example: Limiting coverage by object owner and name. -```sql +```sql linenums="1" begin ut.run( 'unit_test_schema', ut_coverage_html_reporter(), @@ -272,7 +289,7 @@ Executes all tests in schema `unit_test_schema`. Coverage will only be reported The `a_exclude_objects` can be used in the same way as `a_include_objects`. Example: Excluding objects from coverage report by providing a list of object owner/name to be excluded. -```sql +```sql linenums="1" begin ut.run( 'unit_test_schema.test_award_bonus', ut_coverage_html_reporter(), @@ -282,12 +299,12 @@ end; ``` Executes test `test_award_bonus` in schema `unit_test_schema`. Coverage will be reported on all objects in schema `ut3_user` except the `betwnstr` object. -**Note** -> Filtering using `a_include_objects` and `a_exclude_objects` is only applicable when gathering coverage for a schema. Those filters are not applied when reporting coverage on project files. +!!! note + Filtering using `a_include_objects` and `a_exclude_objects` is only applicable when gathering coverage for a schema. Those filters are not applied when reporting coverage on project files. -**Note** -> When running coverage on schema objects, all source code of package bodies, functions, procedures, type bodies and triggers that were not executed will be reported as having 0% code coverage and all source code lines will show as uncovered. -> This is different from the behavior when gathering coverage on project files. +!!! note + When running coverage on schema objects, all source code of package bodies, functions, procedures, type bodies and triggers that were not executed will be reported as having 0% code coverage and all source code lines will show as uncovered. + This is different from the behavior when gathering coverage on project files. ### Project based Coverage @@ -300,6 +317,7 @@ They are abstracted from database, schema names, packages, procedures and functi To be able to effectively use reporters dedicated for those tools, utPLSQL provides functionality for mapping database object names to project files. There are a few significant differences when running coverage on project files compared to running coverage on schema(s). + - Coverage is only reported on objects that were successfully mapped to project files. - Project files (database objects) that were not executed at all are not reported as fully uncovered. It is up to the consumer (Sonar/Coveralls) to determine if project file should be considered as 0% coverage or just ignored. @@ -335,21 +353,23 @@ C: ``` By default, utPLSQL will convert file paths into database objects using the following regular expression `/(((\w|[$#])+)\.)?((\w|[$#])+)\.(\w{3})$` + - object owner (if it is present) is identified by the expression in the second set of brackets - object name is identified by the expression in the fourth set of brackets - object type is identified by the expression in the sixth set of brackets -**Note** -> utPLSQL will replace any '\\' with '/' for the purpose of mapping files to objects. The paths shown in the results will remain (contain '\' where it was present). -> This is done to simplify the syntax of regular expressions. Regular expression will always use '/' as a directory separator on a file path regardless of whether you're on a Windows or Unix system. +!!! note + utPLSQL will replace any '\\' with '/' for the purpose of mapping files to objects. The paths shown in the results will remain (contain '\' where it was present). + This is done to simplify the syntax of regular expressions. Regular expression will always use '/' as a directory separator on a file path regardless of whether you're on a Windows or Unix system. -**Note** -> Below examples assume that you have downloaded latest version of [utPLSQL-cli](https://github.com/utPLSQL/utPLSQL-cli/releases) and extracted it into your projects root directory (my_project). -> The examples assume that you run the utPLSQL-cli from `my_project` directory. +!!! note + Below examples assume that you have downloaded latest version of [utPLSQL-cli](https://github.com/utPLSQL/utPLSQL-cli/releases) + and extracted it into your projects root directory + and that you run the utPLSQL-cli from that directory. Windows: -``` +```pwsh utPLSQL-cli\bin\utplsql run test_runner/pass@db_host:db_port/db_service_name ^ -p=hr,hotel ^ -source_path=sources ^ @@ -369,6 +389,7 @@ utPLSQL-cli/bin/utplsql run test_runner/pass@db_host:db_port/db_service_name \ ``` The above commands will: + - connect as user `test_runner` - run all utPLSQL v3 tests for users `hr`, `hotel` - map database code to project files in `sources` directory and save code coverage results into `coverage.html` @@ -419,7 +440,7 @@ Note that the owner/name/type subexpressions don't need to be explicitly specifi In the below example, they were specified explicitly only for `source_path`, `test_path` doesn't have subexpressions specified and so they are default (2/3/4). Windows: -``` +```pwsh utPLSQL-cli\bin\utplsql run test_runner/pass@db_url ^ -p=hr,hotel ^ -source_path=sources ^ @@ -482,7 +503,7 @@ For the database objects mapped to `souces` directory user `code_owner` will be For the database objects mapped to `tests` directory user `tests_owner` will be used. Windows: -``` +```pwsh utPLSQL-cli\bin\utplsql run test_runner/pass@db_url ^ -p=tests_owner ^ -source_path=sources -owner=code_owner ^ @@ -501,9 +522,9 @@ utPLSQL-cli/bin/utplsql run test_runner/pass@db_url \ -f=ut_sonar_test_reporter -o=test_results.xml ``` -**Note** -> When the project folder structure does not provide any information about source code owner and test owner, you can specify the owner for tests and owner for code explicitly. -> Such project configuration supports only single-owner for source code and single owner for tests. +!!! note + When the project folder structure does not provide any information about source code owner and test owner, you can specify the owner for tests and owner for code explicitly. + Such project configuration supports only single-owner for source code and single owner for tests. Tested code is mapped to files in `coverage.html` @@ -580,7 +601,7 @@ C: Windows: -``` +```pwsh utPLSQL-cli\bin\utplsql run test_runner/pass@db_url ^ -p=hr,hotel ^ -source_path=sources ^ @@ -631,7 +652,8 @@ Unit test code is mapped to files in `test_results.xml` #### Object-file mapping rules -In order to allow deterministic and accurate mapping of database source-code into project files, the project directory and file structure needs to meet certain criteria. +In order to allow deterministic and accurate mapping of database source-code into project files, the project directory and file structure needs to meet certain criteria. + - Source code is kept separate from test code (separate directories) - Each database (source-code) object is stored in an individual file. Package/type specification is kept separate from its body. - File name (file path) contains the name of database object @@ -645,6 +667,7 @@ In order to allow deterministic and accurate mapping of database source-code int The `ut.run` command provides interface to map project into database objects when executing tests. While it is much easier to perform mapping directly from command line, it is possible to achieve similar functionality from any SQL client. The main differences when using the `ut.run(...)` command, will be: + - you can only use single reporter and therefore will get only one report from test execution - you need to provide fill list of project files rather than point to `sources` and `tests` directories @@ -696,7 +719,7 @@ C: To execute all tests and map database source code into source file names you could use the following command in any SQL client: -```sql +```sql linenums="1" begin ut.run( ut_varchar2_list('hr','hotel'), @@ -728,7 +751,7 @@ end; ``` To execute all tests and map database tests code into test file names you could use the following command in any SQL client: -```sql +```sql linenums="1" begin ut.run( ut_varchar2_list('hr','hotel'), @@ -773,7 +796,7 @@ Following API calls enable the standalone coverage reporting. - `.get_report_cursor( ... )` - coverage reporters function producing coverage report as ref-cursor Example: -```sql +```sql linenums="1" --SESSION 1 -- gather coverage on code using specific coverage_run_id value declare @@ -790,7 +813,7 @@ end; / ``` -```sql +```sql linenums="1" --SESSION 2 -- alternative approach -- gather coverage on code using specific coverage_run_id value @@ -802,7 +825,7 @@ exec ut_runner.coverage_stop(); ``` -```sql +```sql linenums="1" --SESSION 1 or SESSION2 2 or SESSION 3 -- run after calls in SESSION 1 & 2 are finished -- retrieve coverage report in HTML format coverage_run_id value @@ -816,7 +839,7 @@ select * ); ``` -```sql +```sql linenums="1" --SESSION 1 or SESSION2 2 or SESSION 3 -- run after calls in SESSION 1 & 2 are finished declare @@ -834,20 +857,20 @@ end; ``` Specification of parameters for `get_report` and `get_report_cursor` -```sql +```sql linenums="1" function get_report( a_coverage_options ut_coverage_options, a_client_character_set varchar2 := null ) return ut_varchar2_rows pipelined ``` -```sql +```sql linenums="1" function get_report_cursor( a_coverage_options ut_coverage_options, a_client_character_set varchar2 := null ) return sys_refcursor ``` -```sql +```sql linenums="1" ut_coverage_options( coverage_run_id raw, schema_names ut_varchar2_rows := null, diff --git a/docs/userguide/exception-reporting.md b/docs/userguide/exception-reporting.md index 6a8b3d4e3..824aaecd5 100644 --- a/docs/userguide/exception-reporting.md +++ b/docs/userguide/exception-reporting.md @@ -1,23 +1,28 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) -# Exception handling and reporting - -The utPLSQL is responsible for handling exceptions wherever they occur in the test run. utPLSQL is trapping most of the exceptions so that the test execution is not affected by individual tests or test packages throwing an exception. -The framework provides a full stacktrace for every exception that was thrown. The stacktrace is clean and does not include any utPLSQL library calls in it. +utPLSQL is responsible for handling exceptions wherever they occur in the test run. The framework is trapping most of the exceptions so that the test execution is not affected by individual tests or test packages throwing an exception. +The framework provides a full stacktrace for every exception that was thrown. The reported stacktrace does not include any utPLSQL library calls in it. To achieve rerunability, the package state invalidation exceptions (ORA-04068, ORA-04061) are not handled and test execution will be interrupted if such exceptions are encountered. This is because of how Oracle behaves on those exceptions. Test execution can fail for different reasons. The failures on different exceptions are handled as follows: -* A test package without body - each `--%test` is reported as failed with exception, nothing is executed -* A test package with _invalid body_ - each `--%test` is reported as failed with exception, nothing is executed -* A test package with _invalid spec_ - package is not considered a valid unit test package and is excluded from execution. When trying to run a test package with invalid spec explicitly, exception is raised. Only valid specifications are parsed for annotations -* A test package that is raising an exception in `--%beforeall` - each `--%test` is reported as failed with exception, `--%test`, `--%beforeeach`, `--%beforetest`, `--%aftertest` and `--%aftereach` are not executed. `--%afterall` is executed to allow cleanup of whatever was done in `--%beforeall` -* A test package that is raising an exception in `--%beforeeach` - each `--%test` is reported as failed with exception, `--%test`, `--%beforetest` and `--%aftertest` is not executed. The `--%aftereach` and `--%afterall` blocks are getting executed to allow cleanup of whatever was done in `--%before...` blocks -* A test package that is raising an exception in `--%beforetest` - the `--%test` is reported as failed with exception, `--%test` is not executed. The `--%aftertest`, `--%aftereach` and `--%afterall` blocks are getting executed to allow cleanup of whatever was done in `--%before...` blocks -* A test package that is raising an exception in `--%test` - the `--%test` is reported as failed with exception. The execution of other blocks continues normally -* A test package that is raising an exception in `--%aftertest` - the `--%test` is reported as failed with exception. The execution of other blocks continues normally -* A test package that is raising an exception in `--%aftereach` - each `--%test` is reported as failed with exception. -* A test package that is raising an exception in `--%afterall` - all blocks of the package are executed, as the `--%afterall` is the last step of package execution. Exception in `--%afterall` is not affecting test results. A warning with exception stacktrace is displayed in the summary +| Problem / error | Framework behavior | +|----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| A test package **without body** | each `--%test` is reported as failed with exception, nothing is executed | +| A test package with **invalid body** | each `--%test` is reported as failed with exception, nothing is executed | +| A test package with **invalid spec** | package is not considered a valid unit test package and is excluded from execution. When trying to run a test package with invalid spec explicitly, exception is raised. Only valid specifications are parsed for annotations | +| A test package that is raising an exception in `--%beforeall` | each `--%test` is reported as failed with exception, `--%test`, `--%beforeeach`, `--%beforetest`, `--%aftertest` and `--%aftereach` are not executed. `--%afterall` is executed to allow cleanup of whatever was done in `--%beforeall` | +| A test package that is raising an exception in `--%beforeeach` | each `--%test` is reported as failed with exception, `--%test`, `--%beforetest` and `--%aftertest` is not executed. The `--%aftereach` and `--%afterall` blocks are getting executed to allow cleanup of whatever was done in `--%before...` blocks | +| A test package that is raising an exception in `--%beforetest` | the `--%test` is reported as failed with exception, `--%test` is not executed. The `--%aftertest`, `--%aftereach` and `--%afterall` blocks are getting executed to allow cleanup of whatever was done in `--%before...` blocks | +| A test package that is raising an exception in `--%test` | the `--%test` is reported as failed with exception. The execution of other blocks continues normally | +| A test package that is raising an exception in `--%aftertest` | the `--%test` is reported as failed with exception. The execution of other blocks continues normally | +| A test package that is raising an exception in `--%aftereach` | each `--%test` is reported as failed with exception. | +| A test package that is raising an exception in `--%afterall` | all blocks of the package are executed, as the `--%afterall` is the last step of package execution. Exception in `--%afterall` is not affecting test results. A warning with exception stacktrace is displayed in the summary | + + +!!! warning + If an exception is thrown in an `afterall` procedure then **no failure reported by utPLSQL**.
+ Framework will only report a warning on the suite that the `afterall` belongs to. Example of reporting with exception thrown in `%beforetest`: ```` diff --git a/docs/userguide/expectations.md b/docs/userguide/expectations.md index 20f641e45..54c204d58 100644 --- a/docs/userguide/expectations.md +++ b/docs/userguide/expectations.md @@ -1,12 +1,13 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) + +## Expectation concepts -# Expectation concepts Validation of the code under test (the tested logic of procedure/function etc.) is performed by comparing the actual data against the expected data. utPLSQL uses expectations and matchers to perform the check on the data. Example of an expectation -```sql +```sql linenums="1" begin ut.expect( 'the tested value' ).to_equal('the expected value'); end; @@ -21,6 +22,7 @@ FAILURE ``` Expectation is a combination of: + - the expected value - optional custom message for the expectation - the matcher used to perform comparison @@ -29,7 +31,7 @@ Expectation is a combination of: Matcher defines the comparison operation to be performed on expected (and actual) value. Pseudo-code: -```sql +```sql linenums="1" ut.expect( a_actual {data-type} [, a_message {varchar2}] ).to_( {matcher} ); ut.expect( a_actual {data-type} [, a_message {varchar2}] ).not_to( {matcher} ); ``` @@ -37,7 +39,7 @@ Pseudo-code: Expectations provide two variants of syntax that you can use. Both variants are functionally-equal but give different usage flexibility. Syntax where matcher is passed as parameter to the expectation: -```sql +```sql linenums="1" ut.expect( a_actual ).to_( {matcher} ); ut.expect( a_actual ).not_to( {matcher} ); -- example @@ -45,18 +47,18 @@ Syntax where matcher is passed as parameter to the expectation: ``` Shortcut syntax, where matcher is directly part of expectation: -```sql +```sql linenums="1" ut.expect( a_actual ).to_{matcher}; ut.expect( a_actual ).not_to_{matcher}; --example - ut.expect( 1 ).to_( be_null() ); + ut.expect( 1 ).to_be_null(); ``` When using shortcut syntax you don't need to surround matcher with brackets. Shortcut syntax is provided for convenience. If you would like to perform more dynamic checks in your code, you could pass the matcher into a procedure like in the below example: -```sql +```sql linenums="1" declare procedure do_check( p_actual varchar2, p_matcher ut_matcher ) is begin @@ -79,26 +81,30 @@ SUCCESS Actual: 'Alibaba' (varchar2) was expected to match: 'ali' , modifiers 'i' ``` -**Note:** -> The examples in the document will be only using shortcut syntax, to keep the document brief. +!!! note + In order to keep the document brief, the examples in the document are only using the standalone expectations syntax. + +## Using expectations -# Using expectations There are two ways to use expectations: - by invoking utPLSQL framework to execute suite(s) of utPLSQL tests - without invoking the utPLSQL framework - running expectations standalone ## Running expectations within utPLSQL framework -When expectations are ran as a part of a test suite, the framework tracks: + +When expectations are run as a part of a test suite, the framework tracks: + - status of each expectation - outcomes (messages) produced by each expectation - call stack to each expectation In this case: + - expectation results of are not sent directly to `dbms_output` - utPLSQL Reporters used when running suite decide on how the expectation results are formatted and displayed Example of test suite with an expectation: -```sql +```sql linenums="1" create or replace package test_divide as --%suite(Divide two numbers) @@ -156,17 +162,15 @@ Please read about different options for [running test suites](running-unit-tests ## Running expectations outside utPLSQL framework When expectations are invoked outside of utPLSQL framework the outputs from expectations are redirected straight to `dbms_output`. -**Note:** -> The output from expectation contains call stack trace only when expectation fails. -> Source code of the line which called the expectation is only reported when the line is part of in-database code (package) and the user calling expectation has privileges to see that source code. +!!! note + The output from expectation contains call stack trace only when expectation fails.
+ Source code of the line which called the expectation is only reported when the line is part of in-database code (package) and the user calling expectation has privileges to see that source code. -**Important** -> Please do not use expectations as part of your production code. They are not designed to be used as part of your code. Expectations are meant to be used only as part of your day-to-day testing activities. +!!! warning "**Important**" + Please do not use expectations as part of your production code. They are not designed to be used as part of your code. Expectations are meant to be used only as part of your day-to-day testing activities. -**Note:** -> The examples in the document will be only using standalone expectations, to keep the document brief. -# Matchers +## Matchers utPLSQL provides the following matchers to perform checks on the expected and actual values. - `be_between( a_upper_bound {data-type}, a_lower_bound {data-type} )` @@ -190,7 +194,7 @@ You can provide a custom failure message by passing it as the second parameter t `ut.expect( a_actual {data-type}, a_message {varchar2} ).to_{matcher}` Example: -````sql +````sql linenums="1" exec ut.expect( 'supercat', 'checked superhero-animal was not a dog' ).to_equal('superdog'); ```` @@ -205,6 +209,7 @@ If the message is provided, it is being added to the normal failure message retu This is mostly useful when your expectations accept dynamic content, as you can provide additional context to make failing test results more readable. In most cases, there is no need to provide custom message to expectation. This is because utPLSQL identifies: + - The test used to execute the expectation - The line number where the expectation is placed in your test code - The line text of the expectation @@ -212,7 +217,7 @@ In most cases, there is no need to provide custom message to expectation. This i Custom message is useful, if your expectation is placed in a shared procedure to perform a check and your test is using the procedure multiple times. Example: -```sql +```sql linenums="1" create or replace package shared_expectation_test is --%suite @@ -262,6 +267,7 @@ Finished in .066344 seconds ``` In the tests results window you can see the list of failed expectations for a test as well as: + - the additional message for expectation - the reason why the expectation failed - the line number of the expectation @@ -273,7 +279,7 @@ In the tests results window you can see the list of failed expectations for a te Expectations provide a very convenient way to perform a check on a negated matcher. Syntax to check for matcher evaluating to true: -```sql +```sql linenums="1" begin ut.expect( a_actual {data-type} ).to_{matcher}; ut.expect( a_actual {data-type} ).to_( {matcher} ); @@ -281,7 +287,7 @@ end; ``` Syntax to check for matcher evaluating to false: -```sql +```sql linenums="1" begin ut.expect( a_actual {data-type} ).not_to_{matcher}; ut.expect( a_actual {data-type} ).not_to( {matcher} ); @@ -291,7 +297,7 @@ end; If a matcher evaluated to NULL, then both `to_` and `not_to` will cause the expectation to report failure. Example: -```sql +```sql linenums="1" declare l_actual boolean; begin @@ -312,7 +318,7 @@ FAILURE ``` Since NULL is neither *true* nor *false*, both expectations will report failure. -# Supported data types +## Supported data types The matrix below illustrates the data types supported by different matchers. @@ -336,14 +342,14 @@ The matrix below illustrates the data types supported by different matchers. | **be_within().of_()** | | | | X | X | X | X | X | | | | | | | | | **be_within_pct().of_()** | | | | | X | | | | | | | | | | | -# Expecting exceptions +## Expecting exceptions Testing is not limited to checking for happy-path scenarios. When writing tests, you often want to validate that in specific scenarios, an exception is thrown. Use the `--%throws` annotation, to test for expected exceptions. Example: -```sql +```sql linenums="1" create or replace function divide(x varchar2, y varchar2) return number is begin return x/y; @@ -382,19 +388,19 @@ Finished in .009229 seconds 1 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) ``` -For more details see documentation of the [`--%throws` annotation.](annotations.md#throws-annotation) +For more details see documentation of the [`--%throws` annotation.](annotations.md#throws) -# Matchers +## Matchers You can choose different matchers to validate that your PL/SQL code is working as expected. -## be_between +### be_between Validates that the actual value is between the lower and upper bound. Example: -```sql +```sql linenums="1" declare l_timestamp timestamp := current_timestamp; l_timestamp_tz timestamp with time zone := systimestamp; @@ -446,17 +452,17 @@ SUCCESS Actual: 'Abb' (varchar2) was expected to be between: 'Aba' and 'Abc' ``` -## be_empty +### be_empty Unary matcher that validates if the provided dataset is empty. Can be used with `BLOB`,`CLOB`, `refcursor` or `nested table`/`varray` passed as `ANYDATA` -**Note:** -BLOB/CLOB that is initialized is not NULL but it is actually equal to `empty_blob()`/`empty_clob()`. +!!! note + BLOB/CLOB that is initialized is not NULL but it is actually equal to `empty_blob()`/`empty_clob()`. Example: -```sql +```sql linenums="1" declare l_cursor sys_refcursor; begin @@ -495,11 +501,11 @@ FAILURE at "anonymous block", line 9 ``` -## be_false +### be_false Unary matcher that validates if the provided value is false. Usage: -```sql +```sql linenums="1" begin ut.expect( ( 1 = 0 ) ).to_be_false(); ut.expect( ( 1 = 1 ) ).to_( be_false() ); @@ -523,11 +529,11 @@ SUCCESS Actual: TRUE (boolean) was expected not to be false ``` -## be_greater_or_equal +### be_greater_or_equal Checks if the actual value is greater or equal than the expected. Usage: -```sql +```sql linenums="1" begin ut.expect( sysdate ).to_be_greater_or_equal( sysdate - 1 ); ut.expect( sysdate ).to_( be_greater_or_equal( sysdate + 1 ) ); @@ -551,11 +557,11 @@ SUCCESS Actual: 2019-07-07T22:43:29 (date) was expected not to be greater or equal: 2019-07-08T22:43:29 (date) ``` -## be_greater_than +### be_greater_than Checks if the actual value is greater than the expected. Usage: -```sql +```sql linenums="1" begin ut.expect( 2 ).to_be_greater_than( 1 ); ut.expect( 0 ).to_( be_greater_than( 1 ) ); @@ -579,11 +585,11 @@ SUCCESS Actual: 0 (number) was expected not to be greater than: 1 (number) ``` -## be_less_or_equal +### be_less_or_equal Checks if the actual value is less or equal than the expected. Usage: -```sql +```sql linenums="1" begin ut.expect( 3 ).to_be_less_or_equal( 3 ); ut.expect( 4 ).to_( be_less_or_equal( 3 ) ); @@ -607,11 +613,11 @@ SUCCESS Actual: 4 (number) was expected not to be less or equal: 3 (number) ``` -## be_less_than +### be_less_than Checks if the actual value is less than the expected. Usage: -```sql +```sql linenums="1" begin ut.expect( 3 ).to_be_less_than( 2 ); ut.expect( 0 ).to_( be_less_than( 2 ) ); @@ -635,7 +641,7 @@ FAILURE at "anonymous block", line 5 ``` -## be_like +### be_like Validates that the actual value is like the expected expression. Syntax: @@ -647,7 +653,7 @@ Parameters `a_mask` and `a_escape_char` represent valid parameters of the [Oracl If you use Oracle Database version 11.2.0.4, you may run into Oracle Bug 14402514: WRONG RESULTS WITH LIKE ON CLOB USING ESCAPE CHARACTER. In this case we recommend to use `match` instead of `be_like`. Usage: -```sql +```sql linenums="1" begin ut.expect( 'Lorem_impsum' ).to_be_like( '%rem%'); ut.expect( 'Lorem_impsum' ).to_be_like( '%rem\_i%', '\' ); @@ -673,11 +679,11 @@ SUCCESS Actual: 'Lorem_impsum' (varchar2) was expected not to be like: '%reM%' ``` -## be_not_null +### be_not_null Unary matcher that validates if the actual value is not null. Usage: -```sql +```sql linenums="1" begin ut.expect( to_clob('ABC') ).to_be_not_null(); ut.expect( to_clob('') ).to_( be_not_null() ); @@ -701,11 +707,11 @@ SUCCESS Actual: NULL (varchar2) was expected not to be not null ``` -## be_null +### be_null Unary matcher that validates if the actual value is null. Usage: -```sql +```sql linenums="1" begin ut.expect( '' ).to_be_null(); ut.expect( 0 ).to_( be_null() ); @@ -729,11 +735,11 @@ SUCCESS Actual: 0 (number) was expected not to be null ``` -## be_true +### be_true Unary matcher that validates if the provided value is true. Usage: -```sql +```sql linenums="1" begin ut.expect( ( 1 = 0 ) ).to_be_true(); ut.expect( ( 1 = 1 ) ).to_( be_true() ); @@ -757,13 +763,13 @@ FAILURE at "anonymous block", line 5 ``` -## have_count +### have_count Unary matcher that validates if the provided dataset count is equal to expected value. Can be used with `refcursor`, `json` or `table type` Usage: -```sql +```sql linenums="1" declare l_cursor sys_refcursor; l_collection ut_varchar2_list; @@ -793,7 +799,7 @@ FAILURE at "anonymous block", line 11 ``` -## match +### match Validates that the actual value is matching the expected regular expression. Syntax: @@ -803,7 +809,7 @@ Syntax: Parameters `a_pattern` and `a_modifiers` represent a valid regexp pattern accepted by [Oracle REGEXP_LIKE condition](https://docs.oracle.com/database/121/SQLRF/conditions007.htm#SQLRF00501) Usage: -```sql +```sql linenums="1" begin ut.expect( '123-456-ABcd' ).to_match( '\d{3}-\d{3}-[a-z]{4}', 'i' ); ut.expect( 'some value' ).to_( match( '^some.*' ) ) ; @@ -827,7 +833,7 @@ FAILURE at "anonymous block", line 5 ``` -## equal +### equal The `equal` matcher is very restrictive. Test using this matcher succeeds only when the compared data-types are exactly the same. If you are comparing a `varchar2` to a `number`, it will fail even if the text contains the same numeric value as the number. The matcher will also fail when comparing a `timestamp` to a `timestamp with timezone` data-type etc. @@ -841,7 +847,7 @@ Syntax: `ut.expect( a_actual ).to_equal( a_expected [, a_nulls_are_equal])[.advanced_options]` Example usage -```sql +```sql linenums="1" declare l_actual varchar2(20); l_expected varchar2(20); @@ -908,12 +914,12 @@ FAILURE ``` -**Note:** ->**Comparing NULLs gives success by default ** -The `a_nulls_are_equal` parameter controls the behavior of a `null = null` comparison. -To change the behavior of `NULL = NULL` comparison pass the `a_nulls_are_equal => false` to the `equal` matcher. +!!! note + **by default, comparing NULL to NULL gives success**
+ The `a_nulls_are_equal` parameter controls the behavior of a `null = null` comparison.
+ To change the behavior of `NULL = NULL` comparison pass the `a_nulls_are_equal => false` to the `equal` matcher. -## contain +### contain This matcher supports only compound data-types comparison. It check if the actual set contains all values of expected subset. @@ -928,7 +934,7 @@ The matcher will cause a test to fail if actual data set does not contain some o ![included_set](../images/venn21.gif) **Example 1.** -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -960,7 +966,7 @@ FAILURE When duplicate rows are present in expected data set, actual data set must also include the same amount of duplicates. **Example 2.** -```sql +```sql linenums="1" declare l_actual ut_varchar2_list; l_expected ut_varchar2_list; @@ -988,7 +994,7 @@ The negated version of `contain` ( `not_to_contain` ) is successful only when al ![not_overlapping_set](../images/venn22.gif) **Example 3.** -```sql +```sql linenums="1" declare l_actual ut_varchar2_list; l_expected ut_varchar2_list; @@ -1025,7 +1031,7 @@ FAILURE **Example 4.** -```sql +```sql linenums="1" declare l_actual ut_varchar2_list; l_expected ut_varchar2_list; @@ -1058,7 +1064,7 @@ FAILURE **Example 5.** -```sql +```sql linenums="1" declare l_actual ut_varchar2_list; l_expected ut_varchar2_list; @@ -1094,7 +1100,7 @@ SUCCESS DEF ``` -## to_be_within of +### to_be_within of Determines whether expected value is within range (tolerance) from another value. @@ -1131,7 +1137,7 @@ The distance must be expressed as a non-negative number or non-negative interval > The behavior is similar to a call to `months_between()` function with results rounded to full monts ie. round(months_between(date, date)) **Example 1.** -```sql +```sql linenums="1" begin ut.expect(3).to_be_within(1).of_(4); end; @@ -1139,7 +1145,7 @@ end; ``` **Example 2.** -```sql +```sql linenums="1" begin ut.expect(3).to_be_within(1).of_(5); end; @@ -1157,7 +1163,7 @@ Failures: ``` **Example 3.** -```sql +```sql linenums="1" begin ut.expect(sysdate).to_be_within(interval '1' day).of_(sysdate+2); end; @@ -1175,7 +1181,7 @@ Failures: ``` -## to_be_within_pct of +### to_be_within_pct of Determines whether actual value is within percentage range of expected value. The matcher only works with `number` data-type. @@ -1187,7 +1193,7 @@ The formula used for calcuation of expectation is: ``` **Example 1.** -```sql +```sql linenums="1" begin ut.expect(9).to_be_within_pct(10).of_(10); end; @@ -1203,6 +1209,7 @@ SUCCESS ## Comparing cursors, object types, nested tables and varrays utPLSQL is capable of comparing compound data-types including: + - ref cursors - object types - nested table/varray types @@ -1224,6 +1231,7 @@ utPLSQL is capable of comparing compound data-types including: See [issue #880](https://github.com/utPLSQL/utPLSQL/issues/880) for details. *Note: This behavior might be fixed in future releases, when utPLSQL is no longer depending on XMLType for compound data comparison.* utPLSQL offers advanced data-comparison options, for comparing compound data-types. The options allow you to: + - define columns/attributes to exclude from comparison - define columns/attributes to include in comparison - and more ... @@ -1234,6 +1242,7 @@ For details on available options and how to use them, read the [advanced data co When comparing compound data, utPLSQL will determine the difference between the expected and the actual data. The diff includes: + - differences in column names, column positions and column data-type for cursor data - only data in columns/rows that differ @@ -1243,9 +1252,9 @@ Consider the following expected cursor data | ID (NUMBER)| FIRST_NAME (VARCHAR2) | LAST_NAME (VARCHAR2) | SALARY (NUMBER) | |:----------:|:----------------------:|:----------------------:|:---------------:| -| 1 | JACK | SPARROW | 10000 | -| 2 | LUKE | SKYWALKER | 1000 | -| 3 | TONY | STARK | 1000000 | +| 1 | JACK | SPARROW | 10000 | +| 2 | LUKE | SKYWALKER | 1000 | +| 3 | TONY | STARK | 1000000 | And the actual cursor data: @@ -1258,6 +1267,7 @@ And the actual cursor data: The two data-sets above have the following differences: + - column ID is misplaced (should be first column but is last) - column SALARY has data-type VARCHAR2 but should be NUMBER - column GENDER exists in actual but not in the expected (it is an Extra column) @@ -1269,7 +1279,7 @@ The two data-sets above have the following differences: utPLSQL will report all of the above differences in a readable format to help you identify what is not correct in the compared dataset. Below example illustrates, how utPLSQL will report such differences. -```sql +```sql linenums="1" declare l_actual sys_refcursor; l_expected sys_refcursor; @@ -1317,11 +1327,13 @@ FAILURE ``` utPLSQL identifies and reports on columns: + - column misplacement - column data-type mismatch - extra/missing columns When comparing rows utPLSQL: + - reports only mismatched columns when rows match - reports columns existing in both data-sets when whole row is not matching - reports whole extra (not expected) row from actual when actual has extra rows @@ -1331,6 +1343,7 @@ When comparing rows utPLSQL: ### Object and nested table data-type comparison examples When comparing object type / nested table / varray, utPLSQL will check: + - if data-types match - if data in the compared elements is the same. @@ -1340,7 +1353,7 @@ When diffing, utPLSQL will not check name and data-type of individual attribute Below examples demonstrate how to compare object and nested table data-types. Object type comparison. -```sql +```sql linenums="1" create type department as object(name varchar2(30)) / @@ -1368,7 +1381,7 @@ FAILURE ``` Table type comparison. -```sql +```sql linenums="1" create type department as object(name varchar2(30)) / create type departments as table of department @@ -1406,7 +1419,7 @@ FAILURE ``` Some of the possible combinations of anydata and their results: -```sql +```sql linenums="1" clear screen set serverout on set feedback off @@ -1590,18 +1603,18 @@ FAILURE ### Comparing cursor data containing DATE fields -**Important note** +!!! warning "Important" -utPLSQL uses XMLType internally to represent rows of the cursor data. This is by far the most flexible method and allows comparison of cursors containing LONG, CLOB, BLOB, user defined types and even nested cursors. -Due to the way Oracle handles DATE data type when converting from cursor data to XML, utPLSQL has no control over the DATE formatting. -The NLS_DATE_FORMAT setting from the moment the cursor was opened determines the formatting of dates used for cursor data comparison. -By default, Oracle NLS_DATE_FORMAT is timeless, so data of DATE datatype, will be compared ignoring the time component. + utPLSQL uses XMLType internally to represent rows of the cursor data. This is by far the most flexible method and allows comparison of cursors containing LONG, CLOB, BLOB, user defined types and even nested cursors.
+ Due to the way Oracle handles DATE data type when converting from cursor data to XML, utPLSQL has no control over the DATE formatting.
+ The NLS_DATE_FORMAT setting from the moment the cursor was opened determines the formatting of dates used for cursor data comparison.
+ By default, Oracle NLS_DATE_FORMAT is timeless, so data of DATE datatype, will be compared ignoring the time component.
You should surround cursors and expectations with procedures `ut.set_nls`, `ut.reset_nls`. This way, the DATE data in cursors will be properly formatted for comparison using date-time format. The example below makes use of `ut.set_nls`, `ut.reset_nls`, so that the date in `l_expected` and `l_actual` is compared using date-time formatting. -```sql +```sql linenums="1" clear screen alter session set nls_date_format='yyyy-mm-dd'; set serverout on @@ -1646,6 +1659,7 @@ drop table events; ``` In the above example: + - The first expectation is successful, as the `l_expected` cursor contains different date-time then the cursor returned by `get_events` function call - The second expectation fails, as the column `event_date` will get compared as DATE without TIME (using default current session NLS date format) @@ -1684,7 +1698,7 @@ This applies to `timestamp`,`timestamp with timezone`, `timestamp with local tim Example below illustrates usage of `cast` operator to assure appropriate precision is applied on timestamp bind-variables in cursor result-set -```sql +```sql linenums="1" clear screen set serverout on set feedback off @@ -1747,12 +1761,12 @@ FAILURE at "anonymous block", line 32 ``` -# Comparing Json objects +## Comparing Json objects utPLSQL is capable of comparing json data-types of `json_element_t` **on Oracle 12.2 and above**, and also `json` **on Oracle 21 and above** -**Note:** -> Whenever a database is upgraded to compatible version the utPLSQL needs to be reinstalled to pick up json changes. E.g. upgrade from 18c to 21c to enable `json` type compare. +!!! note + Whenever a database is upgraded to compatible version the utPLSQL needs to be reinstalled to pick up json changes. E.g. upgrade from 18c to 21c to enable `json` type compare. ### Notes on comparison of json data @@ -1766,7 +1780,7 @@ utPLSQL is capable of comparing json data-types of `json_element_t` **on Oracle Compare JSON example using `json_element_t`: -```sql +```sql linenums="1" declare l_expected json_element_t; l_actual json_element_t; @@ -1848,7 +1862,7 @@ FAILURE ``` Comparing parts of JSON example using `json_element_t` subtypes: -```sql +```sql linenums="1" declare l_actual json_object_t; l_actual_extract json_array_t; diff --git a/docs/userguide/getting-started.md b/docs/userguide/getting-started.md index cd2f41af6..1ff54a7d6 100644 --- a/docs/userguide/getting-started.md +++ b/docs/userguide/getting-started.md @@ -1,4 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) # Getting started with TDD and utPLSQL @@ -7,7 +7,7 @@ utPLSQL is designed in a way that allows you to follow Below is an example of building a simple function with TDD. -# Gather requirements +## Gather requirements We have a requirement to build a function that will return a substring of a string that is passed to the function. @@ -17,14 +17,14 @@ The function should accept three parameters: - start_position - end_position -# Create a test +## Create a test We will start from the bare minimum and move step by step, executing tests every time we make minimal progress. This way, we assure we don't jump ahead too much and produce code that is untested or untestable. -## Create test package +### Create test package -```sql +```sql linenums="1" create or replace package test_betwnstr as --%suite(Between string function) @@ -43,9 +43,9 @@ Finished in .451423 seconds 0 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) ``` -## Define specification for the test +### Define specification for the test -```sql +```sql linenums="1" create or replace package test_betwnstr as --%suite(Between string function) @@ -76,9 +76,9 @@ Finished in .509673 seconds Well our test is failing as the package specification requires a body. -## Define body of first test +### Define body of first test -```sql +```sql linenums="1" create or replace package body test_betwnstr as procedure basic_usage is @@ -110,11 +110,11 @@ Finished in .415851 seconds Our test is failing as the test suite package body is invalid. Looks like we need to define the function we want to test. -# Implement code to fulfill the requirement +## Implement code to fulfill the requirement -## Define tested function +### Define tested function -```sql +```sql linenums="1" create or replace function betwnstr( a_string varchar2, a_start_pos integer, a_end_pos integer ) return varchar2 is begin @@ -143,11 +143,11 @@ Finished in .375178 seconds So now we see that our test works but the function does not return the expected results. Let us fix this and continue from here. -## Fix the tested function +### Fix the tested function The function returned a string one character short, so we need to add 1 to the substr parameter. -```sql +```sql linenums="1" create or replace function betwnstr( a_string varchar2, a_start_pos integer, a_end_pos integer ) return varchar2 is begin @@ -169,14 +169,14 @@ Finished in .006077 seconds So our test is now passing, great! -# Refactor +## Refactor Once our tests are passing, we can safely refactor (restructure) the code as we have a safety harness in place to ensure that after the restructuring and cleanup of the code, everything is still working. One thing worth mentioning is that refactoring of tests is as important as refactoring of code. Maintainability of both is equally important. -# Further requirements +## Further requirements It seems like our work is done. We have a function that returns a substring from start position to end position. As we move through the process of adding tests, it's very important to think about edge cases. @@ -195,12 +195,12 @@ Here is a list of edge cases for our function: We should define expected behavior for each of these edge cases. Once defined we can start implementing tests for those behaviors and adjust the tested function to meet the requirements specified in the tests. -## Add test for additional requirement +### Add test for additional requirement A new requirement was added: Start position zero - should be treated as start position one -```sql +```sql linenums="1" create or replace package test_betwnstr as --%suite(Between string function) @@ -250,11 +250,11 @@ Finished in .232584 seconds Looks like our function does not work as expected for zero start position. -## Implementing the requirement +### Implementing the requirement Let's fix our function so that the new requirement is met -```sql +```sql linenums="1" create or replace function betwnstr( a_string varchar2, a_start_pos integer, a_end_pos integer ) return varchar2 is begin @@ -281,14 +281,14 @@ Finished in .012718 seconds Great! We have made some visible progress. -## Refactoring +### Refactoring When all tests are passing we can proceed with a safe cleanup of our code. The function works well, but we use the `return` twice, which is not the best coding practice. An alternative implementation could be cleaner. -```sql +```sql linenums="1" create or replace function betwnstr( a_string varchar2, a_start_pos integer, a_end_pos integer ) return varchar2 is begin @@ -308,7 +308,7 @@ Finished in .013739 seconds 2 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) ``` -# Remaining requirements +## Remaining requirements You may continue on with the remaining edge cases from here. diff --git a/docs/userguide/install.md b/docs/userguide/install.md index fbe3b44a1..9d8167739 100644 --- a/docs/userguide/install.md +++ b/docs/userguide/install.md @@ -1,8 +1,9 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) -# Supported database versions +## Supported database versions utPLSQL is continuously tested against following versions of Oracle databases + * 11g R2 * 12c * 12c R2 @@ -11,17 +12,17 @@ utPLSQL is continuously tested against following versions of Oracle databases We do our best to assure full compatibility with supported versions of Oracle databases [See](http://www.oracle.com/us/support/library/lifetime-support-technology-069183.pdf#page=6) -# Requirements +## Requirements utPLSQL will run on any Oracle Database version 11g relase 2 or above. -## Licensed features required +### Licensed features required utPLSQL doesn't require any extra licensed features of Oracle database. It can be installed on any Standard Edition Oracle Database. In fact, it even supports Oracle 11g XE which is a free Oracle Database version with minimal features and storage limits. -## Storage requirements +### Storage requirements utPLSQL will use tablespace for the following: - storage of annotation cache @@ -38,9 +39,9 @@ Profiler results may require regular purging to assure low space consumption. utPLSQl does not purge profiler tables as those tables can can be shared with other tools. -# Downloading utPLSQL +## Downloading utPLSQL -## Manual download +### Manual download - Go to GitHub releases page for utPLSQL [`https://github.com/utPLSQL/utPLSQL/releases`](https://github.com/utPLSQL/utPLSQL/releases) - Choose the version to download - latest is always greatest @@ -51,13 +52,13 @@ utPLSQl does not purge profiler tables as those tables can can be shared with ot The files have identical content but use different compression (tar / zip ) so choose whichever you prefer depending on your platform (Win/Mac/Unix/Linux). -## Scripted download of latest utPLSQL version +### Scripted download of latest utPLSQL version The below snippets can be used to download latest version of utPLSQL from github releases. After downloading follow the installation instructions in next sections of this document. -### Unix/Linux +#### Unix/Linux ```bash #!/bin/bash @@ -75,7 +76,7 @@ You may download with a one-liner if that is more convenient. curl -LOk $(curl --silent https://api.github.com/repos/utPLSQL/utPLSQL/releases/latest | awk '/browser_download_url/ { print $2 }' | grep ".zip\"" | sed 's/"//g') ``` -### Windows +#### Windows To run the script on windows you will need [PowerShell 3.0](https://blogs.technet.microsoft.com/heyscriptingguy/2013/06/02/weekend-scripter-install-powershell-3-0-on-windows-7/) or above. You will also need .NET 4.0 Framework or above. @@ -104,7 +105,7 @@ foreach ($i in $urlList) { } ``` -# Headless installation +## Headless installation utPLSQL can be installed with DDL trigger, to enable tracking of DDL changes to your unit test packages. This is the recommended installation approach, when you want to compile and run unit test packages in a schema containing huge amount of database packages (for example Oracle EBS installation schema). @@ -115,28 +116,20 @@ This process can be time-consuming if DB schema is large. The headless scripts accept three optional parameters that define: - username to create as owner of utPLSQL (default `ut3`) - password for owner of utPLSQL (default `XNtxj8eEgA6X6b6f`) -- tablespace to use for storage of profiler data (default `users`) +- tablespace to use for storage of profiler and utPLSQL cache data (default `users`) The scripts need to be executed by `SYSDBA`, in order to grant access to `DBMS_LOCK` and `DBMS_CRYPTO` system packages. -**Note:** -> Grant on `DBMS_LOCK` is required only for installation on Oracle versions below 18c. For versions 18c and above, utPLSQL uses `DBMS_SESSION.SLEEP` so access to `DBMS_LOCK` package is no longer needed. - -**Note:** -> The user performing the installation must have the `ADMINISTER DATABASE TRIGGER` privilege. This is required for installation of trigger that is responsible for parsing annotations at at compile-time of a package. - -**Note:** -> When installing with DDL trigger, utPLSQL will not be registering unit tests for any of oracle-maintained schemas. -For Oracle 11g following users are excluded: -> ANONYMOUS, APPQOSSYS, AUDSYS, DBSFWUSER, DBSNMP, DIP, GGSYS, GSMADMIN_INTERNAL, GSMCATUSER, GSMUSER, ORACLE_OCM, OUTLN, REMOTE_SCHEDULER_AGENT, SYS, SYS$UMF, SYSBACKUP, SYSDG, SYSKM, SYSRAC, SYSTEM, WMSYS, XDB, XS$NULL -> -> For Oracle 12c and above the users returned by below query are excluded by utPLSQL: -> ->```sql -> select username from all_users where oracle_maintained='Y'; ->``` +!!! warning "Important" + - `DBMS_LOCK` is required for session synchronization between main session and session consuming realtime reports.
+ - The user performing the installation must have the `ADMINISTER DATABASE TRIGGER` privilege. This is required for installation of trigger that is responsible for parsing annotations at at compile-time of a package.
+ - When installed with DDL trigger, utPLSQL will not be registering unit tests for any of oracle-maintained schemas.
+ - For Oracle 11g following users are excluded:
+ ANONYMOUS, APPQOSSYS, AUDSYS, DBSFWUSER, DBSNMP, DIP, GGSYS, GSMADMIN_INTERNAL, GSMCATUSER, GSMUSER, ORACLE_OCM, OUTLN, REMOTE_SCHEDULER_AGENT, SYS, SYS$UMF, SYSBACKUP, SYSDG, SYSKM, SYSRAC, SYSTEM, WMSYS, XDB, XS$NULL
+ - For Oracle 12c and above the users returned by below query are excluded by utPLSQL:
+ `select username from all_users where oracle_maintained='Y';`
-## Installation without DDL trigger +### Installation without DDL trigger To install the utPLSQL into a new database schema and grant it to public, execute the script `install_headless.sql` as SYSDBA. @@ -152,7 +145,7 @@ cd source sqlplus sys/sys_pass@db as sysdba @install_headless.sql utp3 my_verySecret_password utp3_tablespace ``` -## Installation with DDL trigger +### Installation with DDL trigger To install the utPLSQL into a new database schema and grant it to public, execute the script `install_headless_with_trigger.sql` as SYSDBA. @@ -168,7 +161,7 @@ cd source sqlplus sys/sys_pass@db as sysdba @install_headless_with_trigger.sql utp3 my_verySecret_password utp3_tablespace ``` -# Recommended Schema +## Recommended Schema It is highly recommended to install utPLSQL in it's own schema. You are free to choose any name for this schema. Installing uPLSQL into a shared schema is really not recommended as you loose isolation of framework. @@ -190,9 +183,9 @@ utPLSQL is using [DBMS_PROFILER tables](https://docs.oracle.com/cd/E18283_01/app It is up to DBA to maintain the storage of the profiler tables. -# Manual installation procedure +## Manual installation procedure -## Creating schema for utPLSQL +### Creating schema for utPLSQL To create the utPLSQL schema and grant all the required privileges execute script `create_utplsql_owner.sql` from the `source` directory with parameters: - `user name` - the name of the user that will own of utPLSQL object @@ -205,7 +198,7 @@ cd source sqlplus sys/sys_password@database as sysdba @create_utPLSQL_owner.sql ut3 ut3 users ``` -## Installing utPLSQL +### Installing utPLSQL To install the utPLSQL framework into your database, go to `source` directory, run the `install.sql` providing the `schema_name` for utPLSQL as parameter. Schema must be created prior to calling the `install` script. You may install utPLSQL from any account that has sufficient privileges to create objects in other users schema. @@ -216,7 +209,7 @@ cd source sqlplus admin/admins_password@database @install.sql ut3 ``` -## Installing DDL trigger +### Installing DDL trigger To minimize startup time of utPLSQL framework (especially on a database with large schema) it is recommended to install utPLSQL DDL trigger to enable utPLSQL annotation to be updated at compile-time. It's recommended to install DDL trigger when connected as `SYSDBA` user. Trigger is created in utPLSQL schema. @@ -231,11 +224,11 @@ cd source sqlplus admin/admins_password@database @install_ddl_trigger.sql ut3 ``` -**Note:** ->Trigger can be installed ant any point in time. +!!! note + Trigger can be installed ant any point in time after the utPLSQL installation. The framework will detect the presence of DDL trigger and act accordingly. -## Allowing other users to access the utPLSQL framework +### Allowing other users to access the utPLSQL framework In order to allow other users to access utPLSQL, synonyms must be created and privileges granted. You have two options: @@ -264,21 +257,21 @@ The following tools that support the SQL*Plus commands can be used to run the in - [SQLcl](http://www.oracle.com/technetwork/developer-tools/sqlcl/overview/index.html) - [Oracle SQL Developer](http://www.oracle.com/technetwork/developer-tools/sql-developer/overview/index.html) -# Checking environment and utPLSQL version +## Checking environment and utPLSQL version To check the framework version execute the following query: -```sql +```sql linenums="1" select substr(ut.version(),1,60) as ut_version from dual; ``` Additionally you may retrieve more information about your environment by executing the following query: -```sql +```sql linenums="1" select xmlserialize( content xmltype(ut_run_info()) as clob indent size = 2 ) from dual; ``` -# Additional requirements +## Additional requirements In order to use the Code Coverage functionality of utPLSQL, users executing the tests must have the CREATE privilege on the PLSQL code that the coverage is gathered on. This is a requirement of [DBMS_PROFILER package](https://docs.oracle.com/cd/E18283_01/appdev.112/e16760/d_profil.htm#i999476). @@ -286,7 +279,7 @@ This is a requirement of [DBMS_PROFILER package](https://docs.oracle.com/cd/E182 In practice, user running tests for PLSQL code that he does not own, needs to have CREATE ANY PROCEDURE/CREATE ANY TRIGGER privileges. Running code coverage on objects that the user does not own will **not produce any coverage information** without those privileges. -# Uninstalling utPLSQL +## Uninstalling utPLSQL To uninstall run `uninstall.sql` and provide `schema_name` where utPLSQL is installed. @@ -308,11 +301,11 @@ i.e. the uninstall script provided with version 3.1.11 will not work correctly Alternatively you can drop the user that owns utPLSQL and re-create it using headless install. -# Version upgrade +## Version upgrade Currently, the only way to upgrade version of utPLSQL v3.0.0 and above is to remove the previous version and install the new version. -# Working with utPLSQL v2 +## Working with utPLSQL v2 If you are using utPLSQL v2, you can still install utPLSQL v3. The only requirement is that utPLSQL v3 needs to be installed in a different schema than utPLSQL v2. diff --git a/docs/userguide/querying_suites.md b/docs/userguide/querying_suites.md index 136770405..24159fede 100644 --- a/docs/userguide/querying_suites.md +++ b/docs/userguide/querying_suites.md @@ -1,7 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -# Qyerying for test suites - +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) ## Obtaining information about suites @@ -25,27 +22,27 @@ Querying the data from function provides the follwing details: - `tags` - tags associated with suites To get list of all test suites in current schema -```sql +```sql linenums="1" select * from table(ut_runner.get_suites_info()) where item_type = 'UT_SUITE'; ``` To get list of all tests for test suite `TEST_STUFF` in current user schema -```sql +```sql linenums="1" select * from table(ut_runner.get_suites_info(USER, 'TEST_STUFF')) where item_type = 'UT_TEST'; ``` To get a full information about suite `TEST_STUFF` including suite description, all contexts and tests in a suite -```sql +```sql linenums="1" select * from table(ut_runner.get_suites_info(USER, 'TEST_STUFF')) where item_type = 'UT_TEST'; ``` To get a full information about suites that have a path like `ut3:tests.test_package_*` including suite description, all contexts and tests in a suite -```sql +```sql linenums="1" select * from table(ut_runner.get_suites_info('ut3:tests.test_package_*') where item_type = 'UT_TEST'; ``` To get a full information about suites that have object name like `test_package_*` including suite description, all contexts and tests in a suite -```sql +```sql linenums="1" select * from table(ut_runner.get_suites_info('test_package_*')); ``` @@ -54,7 +51,7 @@ select * from table(ut_runner.get_suites_info('test_package_*')); Function `ut_runner.has_suites(a_owner)` returns boolean value indicating if given schema contains test suites. Example: -```sql +```sql linenums="1" begin if ut_runner.has_suites(USER) then dbms_output.put_line( 'User '||USER||' owns test suites' ); @@ -69,7 +66,7 @@ end; Function `ut_runner.is_suite(a_owner, a_package_name) ` returns boolean value indicating if given package is a test suites. Example: -```sql +```sql linenums="1" begin if ut_runner.is_suite(USER,'TEST_STUFF') then dbms_output.put_line( 'Package '||USER||'.TEST_STUFF is a test suite' ); @@ -84,7 +81,7 @@ end; Function `ut_runner.is_test(a_owner, a_package_name, a_procedure_name) ` returns boolean value indicating if given package is a test suites. Example: -```sql +```sql linenums="1" begin if ut_runner.is_test(USER,'TEST_STUFF','A_TEST_TO_CHECK_STUFF') then dbms_output.put_line( 'Procedure '||USER||'.TEST_STUFF.A_TEST_TO_CHECK_STUFF is a test' ); diff --git a/docs/userguide/reporters.md b/docs/userguide/reporters.md index 14d411aa6..24d1065fe 100644 --- a/docs/userguide/reporters.md +++ b/docs/userguide/reporters.md @@ -1,8 +1,8 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) -utPLSQL provides the following reporting formats. +utPLSQL provides several reporting formats. The sections below describe most of them. -# Documentation reporter +## Documentation reporter The `ut_documentation_reporter` is the default reporting format used by the framework. It provides a human readable test results. @@ -17,7 +17,8 @@ Example outputs from documentation reporter. ![doc_reporter_outputs](../images/documentation_reporter.png) -The documentation report provides the following information. +Documentation reporter provides the following information. + - Test suite name or test package name (nested with suitepath if suitepath is used) - Test description name or test procedure name - Information about test failing `(FAILED - n)` @@ -26,7 +27,7 @@ The documentation report provides the following information. - Summary with total number of tests, number of tests with status and timing for the execution -## Color output from documentation reporter +### Color output from documentation reporter When invoking tests with documentation reporter and your command line supports ANSICONSOLE (default on Unix) [available for Windows](http://adoxa.altervista.org/ansicon/), you can obtain the coloured outputs from the documentation reporter. @@ -41,7 +42,7 @@ Example outputs from documentation reporter. ![doc_reporter_outputs](../images/documentation_reporter_color.png) -# JUnit reporter +## JUnit reporter Most of continuous integration servers (like Jenkins) are capable of consuming unit test execution results in [JUnit](https://en.wikipedia.org/wiki/JUnit) format. The `ut_junit_reporter` in earlier version referred as `ut_xunit_reporter` is producing outcomes as JUnit-compatible XML unit test report, that can be used by CI servers to display their custom reports and provide metrics (like tests execution trends). @@ -63,7 +64,7 @@ Example of failure report details -# Teamcity reporter +## Teamcity reporter [Teamcity](https://www.jetbrains.com/teamcity/) is a CI server by Jetbrains. It supports JUnit reporting and additionally has it's own format of reporting that allows tracking of progress of a CI step/task as it executes. The TeamCity format developed by Jetbrains is supported by utPLSQL with `ut_teamcity_reporter`. @@ -83,9 +84,12 @@ Example of failure report details ![junit_reporter_outputs_errors](../images/teamcity_report_example_errors.png) -# Sonar test reporter -If you are using [SonarQube](https://www.sonarqube.org/) or [SonarCloud](https://about.sonarcloud.io/) to do static code analysis for you PLSQL projects, your code analysis can benefit from code coverage and test results. +## Sonar test reporter + +If you are using [SonarQube](https://www.sonarqube.org/) or [SonarCloud](https://about.sonarcloud.io/) to do static code analysis for you PLSQL projects, +your code analysis can benefit from code coverage and test results. utPLSQL provides two reporters to for SonarQube: + - `ut_sonar_test_reporter` - provides an XML output of each test executed per each project test file (package) - `ut_coverage_sonar_reporter` - provides XML output of code coverage per each project source file @@ -97,15 +101,15 @@ The paths to files can be relative to the project root directory (recommended) o Providing invalid paths or paths to non-existing files will result in failure when publishing test results/coverage results to sonar server. -For details on how to invoke reporter with paths, see the **Coverage reporters** section. +For details on how to invoke reporter with paths, see the [Code coverage](coverage.md) section. -# TFS / VSTS Reporter -If you are using [TFS](https://www.visualstudio.com/tfs/) or [VSTS](https://www.visualstudio.com/team-services/) to do static code analysis for you PLSQL projects and run builds, your code analysis can benefit from code coverage and test results. TFS reporter is designed specifically to [work with Microsoft Team Fundation Server](https://docs.microsoft.com/en-us/vsts/build-release/tasks/test/publish-test-results?view=vsts) report format which is very old version of [JUnit](https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd). -Main diffrence between standard JUnit is that elements cannot be nested and attribute skipped is not present. +## TFS / VSTS Reporter -utPLSQL provides test reporter to for TFS / VSTS server: -- `ut_tfs_junit_reporter` - provides an XML output of each test executed per each project test file (package) +If you are using [TFS](https://www.visualstudio.com/tfs/) or [VSTS](https://www.visualstudio.com/team-services/) to do static code analysis for you PLSQL projects and run builds, +your code analysis can benefit from code coverage and test results. TFS reporter is designed specifically to [work with Microsoft Team Fundation Server](https://docs.microsoft.com/en-us/vsts/build-release/tasks/test/publish-test-results?view=vsts) report format which is very old version of [JUnit](https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd). +Main difference between standard JUnit is that elements cannot be nested and attribute skipped is not present. +utPLSQL provides a dedicated `ut_tfs_junit_reporter` reporter to for TFS / VSTS servers. The reporter provides an XML output of each test executed per each project test file (package). Example of test report from TFS CI server. Summary: @@ -117,12 +121,12 @@ Details: ![tfs_junit_reporter_outputs](../images/tfs_details.png) -# Coverage reporters - -utPLSQL comes with a set of build-in coverage reporters. Have a look into the [coverage documentation](coverage.md) to learn more about them. +## Coverage reporters +utPLSQL comes with a set of build-in coverage reporters. +[Code coverage](coverage.md) section describes in details how to use configure and use code coverage. -# Debug reporter +## Debug reporter The `ut_debug_reporter` provides a highly verbose output containing thorough details about framework and test execution. @@ -131,6 +135,7 @@ Use this reporter only when you need to investigate framework issues or raise a Usage of this reporter might have impact on performance of test-suite execution. Amongst others, reporter provides the following information: + - framework version - database version - database OS @@ -152,14 +157,40 @@ Amongst others, reporter provides the following information: - every test - every expectation and it's result -Some of the information in debug log might be redundant. +Some information in debug log might be redundant. -**Note:** ->Some of the information in debug log may be sensitive. In particular: -> - expectation results and messages (logged even for successful runs) -> - test structure -> - db object names -> - etc. +!!! note + Some information in debug log may be sensitive. In particular:
+ - expectation results and messages (logged even for successful runs)
+ - test structure
+ - db object names
+ - etc. - - \ No newline at end of file +## Custom reporters + +It is possible to add your own reporters by creating an appropriate object type. +In principle, it has to be a subtype of `ut_reporter_base`. However, if the reporter is expected to produce output consumable by a client oustside of the database (e.g. the data has to be reported to the screen or to a file), then you should base it on `ut_output_reporter_base` (which is a subtype of `ut_reporter_base`). In contrast, if you would like to create a reporter that, for example, saves the data to a database table, then it should be based directly on `ut_reporter_base`. (Currently, all reporters in the utPLSQL framework are based on `ut_output_reporter_base`.) Coverage reporters are based on `ut_coverage_reporter_base` (a subtype of `ut_output_reporter_base`). + +If you need to produce a colored text output from the custom reporter, then you can build it basing on `ut_console_reporter_base` (a subtype of `ut_output_reporter_base`). In many cases it may also be more convenient to create the custom reporter type under a more specialized type, like `ut_documentation_reporter` or `ut_junit_reporter`, and override just some of the functionality. + +It is recommended to create the reporter type in the schema where utPLSQL is installed (by default it is the `UT3` schema). Note that before running the utPLSQL uninstall scripts, all custom reporters should be dropped (cf. [the installation documentation](install.md)). In particular, when upgrading to a newer version of utPLSQL, one has to drop the custom reporters and recreate them after the upgrade. + +!!! note + Please make sure that grants have been added and synonyms created for the custom reporter in order for reporter to be accessible the same way as other reporters. + Assuming that reporter with name `customer_reporter` was created in schema `UT3` +```sql + grant execute on ut3.custom_reporter to public; + create or replace public synonym custom_reporter for ut3.custom_reporter; +``` + + + +!!! note + It is possible, but cumbersome, to use another schema for storing the custom reporters. This requires to create a synonym for the base reporter type in the schema that is going to own the custom reporter, and to provide appropriate grants both to the owner of the custom reporter and to the user running the reporter. After upgrading or reinstalling utPLSQL, the extra privileges need to be recreated. This approach is not recommended. + +Assuming that the custom reporter type is created in the `UT3` schema, to run the tests using a custom reporter just call: `exec ut.run(ut3.custom_reporter_name());`, optionally providing parameter values to the `custom_reporter_name` constructor. + +One may get acquainted with the source code of the standard reporters bundled with utPLSQL (including the coverage reporters) by browsing the [`source/reporters/`](https://github.com/utPLSQL/utPLSQL/tree/develop/source/reporters) directory. The base reporter types `ut_reporter_base`, `ut_output_reporter_base` and `ut_console_reporter_base` are defined in [`source/core/types`](https://github.com/utPLSQL/utPLSQL/tree/develop/source/core/types). The base coverage reporter type `ut_coverage_reporter_base` is in [`source/core/coverage`](https://github.com/utPLSQL/utPLSQL/tree/develop/source/core/coverage). There are also two examples of custom reporters in [`examples/custom_reporters/`](https://github.com/utPLSQL/utPLSQL/tree/develop/examples/custom_reporters), both extending the functionality of `ut_documentation_reporter`: + +* `ut_custom_reporter` accepts an integer parameter `a_tab_size`; it alters the behaviour of `ut_documentation_reporter` by changing the size of the indentation according to the parameter value (by default the indentation is increased). +* `ut_expectations_reporter` accepts a `varchar2` parameter `a_report_all_expectations`; if its value is `'Y'` (which is the default), then the reporter shows the results of all expectations that are run. This stays in contrast with `ut_documentation_reporter`, which shows the results of all tests that are run, but only of the expectations that failed (keep in mind that a single test may consist of several expectations). diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index d833b6c94..375a05125 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -1,6 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -# Running tests +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) utPLSQL framework provides two main entry points to run unit tests from within the database: @@ -10,7 +8,7 @@ utPLSQL framework provides two main entry points to run unit tests from within t These two entry points differ in purpose and behavior. Most of the time you will want to use `ut.run` as `ut_runner.run` is designed for API integration and does not display the results to the screen. -# Running from CI servers and command line +## Running from CI servers and command line The best way to run your tests from CI server or command line is to use the [utPLSQL-cli](https://github.com/utPLSQL/utPLSQL-cli) command line client. @@ -24,7 +22,7 @@ You may download the latest release of the command line client from [here](https ```bash #!/bin/bash # Get the url to latest release "zip" file -DOWNLOAD_URL=$(curl --silent https://api.github.com/repos/utPLSQL/utPLSQL-cli/releases/latest | awk '/zipball_url/ { print $2 }' | sed -r 's/"|,//g') +DOWNLOAD_URL=$(curl --silent https://api.github.com/repos/utPLSQL/utPLSQL-cli/releases/latest | awk '/browser_download_url/ { print $2 }' | grep ".zip\"" | sed 's/"//g') # Download the latest release "zip" file curl -Lk "${DOWNLOAD_URL}" -o utplsql-cli.zip # Extract downloaded "zip" file @@ -32,7 +30,7 @@ unzip -q utplsql-cli.zip ``` -# ut.run +## ut.run The `ut` package contains overloaded `run` procedures and functions. The `run` API is designed to be called directly by a developer when using an IDE/SQL console to execute unit tests. @@ -42,14 +40,14 @@ A single line call is enough to execute a set of tests from one or more schemes. The **procedures** execute the specified tests and produce output to DBMS_OUTPUT using the specified reporter. The **functions** can only be used in SELECT statements. They execute the specified tests and produce outputs as a pipelined data stream to be consumed by a select statement. -## ut.run procedures +### ut.run procedures The examples below illustrate different ways and options to invoke `ut.run` procedures. You can use a wildcard character `*` to call tests by part of their name or to call tests that are located on paths matched by part of path string. Wildcard character can be placed anywhere on the path and can occur mutliple times. Schema name cannot contain a wildcard character whether is in a suitepath call or call by object name. -```sql +```sql linenums="1" alter session set current_schema=hr; set serveroutput on begin @@ -59,7 +57,7 @@ end; Executes all tests in current schema (_HR_). -```sql +```sql linenums="1" set serveroutput on begin ut.run('HR'); @@ -68,7 +66,7 @@ end; Executes all tests in specified schema (_HR_). -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr:com.my_org.my_project'); @@ -78,7 +76,7 @@ end; Executes all tests from all packages that are on the _com.my_org.my_project_ suitepath. Check the [annotations documentation](annotations.md) to find out about suitepaths and how they can be used to organize test packages for your project. -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr:com*'); @@ -87,7 +85,7 @@ end; Executes all tests in schema `hr` from all packages that are on suitepath starting with `com`. -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr:co*.my_*.my_*'); @@ -96,7 +94,7 @@ end; Executes all tests in schema `hr` from all packages that starting with `my_` and all tests starting with `my_*` that are on suitepath starting with `co` . -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test_apply_bonus'); @@ -105,7 +103,7 @@ end; Executes all tests from package _hr.test_apply_bonus_. -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test_apply_bonus.bonus_cannot_be_negative'); @@ -114,37 +112,38 @@ end; Executes single test procedure _hr.test_apply_bonus.bonus_cannot_be_negative_. -```sql +```sql linenums="1" set serveroutput on begin ut.run(ut_varchar2_list('hr.test_apply_bonus','cust')); end; ``` -Executes all tests from package _hr.test_apply_bonus_ and all tests from schema _cust_. +Executes all tests from package _hr.test_apply_bonus_ and all tests from schema _cust_ (passing individual items to be executed as elements of the ut_varchar2_list table type). + -```sql +```sql linenums="1" set serveroutput on begin - ut.run(ut_varchar2_list('hr.test_apply_bonus,cust)'); + ut.run(ut_varchar2_list('hr.test_apply_bonus,cust')); end; ``` +Executes all tests from package _hr.test_apply_bonus_ and all tests from schema _cust_ (passing all items as a comma-separated-list of values into a single element of the ut_varchar2_list table type). -Executes all tests from package _hr.test_apply_bonus_ and all tests from schema _cust_. -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test_apply_bonus,cust'); end; ``` -Executes all tests from package _hr.test_apply_bonus_ and all tests from schema _cust_. +Executes all tests from package _hr.test_apply_bonus_ and all tests from schema _cust_ (no explicit ut_varchar2_list table type). Using a list of items to execute allows you to execute a fine-grained set of tests. List can be passed as a comma separated list or a list of *ut_varchar2_list objects* or as a list within ut_varchar2_list. -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test*'); @@ -152,7 +151,7 @@ end; ``` Executes all tests in schema `hr` located in packages starting with name `test`. -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test_apply_bonus.bonus_*'); @@ -168,7 +167,7 @@ Executes test procedures with names starting with `bonus` in package `hr.test_ap The `ut.run` procedures and functions accept `a_reporter` attribute that defines the reporter to be used in the run. You can execute any set of tests with any of the predefined reporters. -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test_apply_bonus', ut_junit_reporter()); @@ -179,7 +178,7 @@ Executes all tests from package _HR.TEST_APPLY_BONUS_ and provide outputs to DBM For details on build-in reporters look at [reporters documentation](reporters.md). -## ut.run functions +### ut.run functions The `ut.run` functions provide exactly the same functionality as the `ut.run` procedures. You may use the same sets of parameters with both functions and procedures. @@ -191,11 +190,11 @@ Functions provide output as a pipelined stream and therefore need to be executed At the end of the run, the transaction is automatically rolled-back and all uncommitted changes are reverted. Example. -```sql +```sql linenums="1" select * from table(ut.run('hr.test_apply_bonus', ut_junit_reporter())); ``` -# ut_runner.run procedures +## ut_runner.run procedures The `ut_runner` package provides an API for integrating utPLSQL with other products. Maven, Jenkins, SQL Develper, PL/SQL Developer, TOAD and others can leverage this API to call utPLSQL. @@ -224,7 +223,7 @@ Running with multiple reporters. - each reporter for each test-run must have a unique `reporter_id`. The `reporter_id` is used between two sessions to identify the data stream Example: -```sql +```sql linenums="1" --main test run ( session 1 ) declare l_reporter ut_realtime_reporter := ut_realtime_reporter(); @@ -236,7 +235,7 @@ end; / ``` -```sql +```sql linenums="1" --report consumer ( session 2 ) set arraysize 1 set pagesize 0 @@ -249,7 +248,7 @@ select * ); ``` -```sql +```sql linenums="1" --alternative version of report consumer ( session 2 ) set arraysize 1 set pagesize 0 @@ -262,16 +261,16 @@ select ``` -# Order of test execution +## Order of test execution -## Default order +### Default order When unit tests are executed without random order, they are ordered by: - schema name - suite path or test package name if `--%suitepath` was not specified for that package - `--%test` line number in package -## Random order +### Random order You can force a test run to execute tests in random order by providing one of options to `ut.run`: - `a_random_test_order` - true/false for procedures and 1/0 for functions @@ -281,14 +280,14 @@ When tests are executed with random order, randomization is applied to single le This is needed to maintain visibility and accessibility of common setup/cleanup `beforeall`/`afterall` in tests. Example: -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test_apply_bonus', a_random_test_order => true); end; ``` -```sql +```sql linenums="1" select * from table(ut.run('hr.test_apply_bonus', a_random_test_order => 1)); ``` @@ -303,42 +302,238 @@ Tests were executed with random order seed '302980531'. If you want to re-run tests using previously generated seed, you may do so by running them with parameter `a_random_test_order_seed` Example: -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test_apply_bonus', a_random_test_order_seed => 302980531); end; ``` -```sql +```sql linenums="1" select * from table(ut.run('hr.test_apply_bonus', a_random_test_order_seed => 302980531)); ``` **Note** >Random order seed must be a positive number within range of 1 .. 1 000 000 000. -# Run by Tags +## Run by Tags In addition to the path, you can filter the tests to be run by specifying tags. Tags are defined in the test / context / suite with the `--%tags`-annotation ([Read more](annotations.md#tags)). Multiple tags are separated by comma. -The framework applies `OR` logic to all specified tags so any test / suite that matches at least one tag will be included in the test run. -```sql -begin - ut.run('hr.test_apply_bonus', a_tags => 'test1,test2'); -end; + +### Tag Expressions + +Tag expressions are boolean expressions created by combining tags with the `!`, `&`, `|` operators. Tag expressions can be grouped using `(` and `)` braces. Grouping tag expressions affects operator precedence. + +Two reserved keywords, `any` and `none`, can be used when creating a tag expression to run tests. +- `any` keyword represents tests and suites with any tags +- `none` keyword represents tests and suites without tags + +These keywords may be combined with other expressions just like normal tags. + +!!! note + When specifying `none`, be aware that it will exclude any tests/suites/contexts contained within a tagged suite. + +| Operator | Meaning | +| -------- | --------| +| ! | not | +| & | and | +| \| | or | + +If you are tagging your tests across multiple dimensions, tag expressions help you to select which tests to execute. When tagging by test type (e.g., micro, integration, end-to-end) and feature (e.g., product, catalog, shipping), the following tag expressions can be useful. + + +| Tag Expression | Selection | +| -------- | --------| +| product | all tests for product | +| catalog \| shipping | all tests for catalog plus all tests for shipping | +| catalog & shipping | all tests that are tagged with both `catalog` and `shipping` tags | +| product & !end-to-end | all tests tagged `product`, except the tests tagged `end-to-end` | +| (micro \| integration) & (product \| shipping) | all micro or integration tests for product or shipping | + + +Taking the last expression above `(micro | integration) & (product | shipping)` + +| --%tags |included in run | +| -------- | --------| +| micro | no | +| integration | no | +| micro | no | +| product | no | +| shipping | no | +| micro | no | +| micro, integration | no | +| product, shipping | no | +| micro, product | yes | +| micro, shipping | yes | +| integration, product | yes | +| integration, shipping | yes | +| integration, micro, shipping | yes | +| integration, micro, product | yes | +| integration, shipping ,product | yes | +| micro, shipping ,product | yes | +| integration, micro, shipping ,product | yes | + + +### Sample execution of test with tags. + +Execution of the test with tag expressions is done using the parameter `a_tags`. +Given a test package `ut_sample_test` defined below + +```sql linenums="1" +create or replace package ut_sample_test is + + --%suite(Sample Test Suite) + --%tags(api) + + --%test(Compare Ref Cursors) + --%tags(complex,fast) + procedure ut_refcursors1; + + --%test(Run equality test) + --%tags(simple,fast) + procedure ut_test; + +end ut_sample_test; +/ + +create or replace package body ut_sample_test is + + procedure ut_refcursors1 is + v_actual sys_refcursor; + v_expected sys_refcursor; + begin + open v_expected for select 1 as test from dual; + open v_actual for select 2 as test from dual; + + ut.expect(v_actual).to_equal(v_expected); + end; + + procedure ut_test is + begin + ut.expect(1).to_equal(0); + end; + +end ut_sample_test; +/ +``` + +```sql linenums="1" +select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'api')); +``` +The above call will execute all tests from `ut_sample_test` package as the whole suite is tagged with `api` + +```sql linenums="1" +select * from table(ut.run(a_tags => 'fast&complex')); +``` +The above call will execute only the `ut_sample_test.ut_refcursors1` test, as only the test `ut_refcursors1` is tagged with `complex` and `fast` + +```sql linenums="1" +select * from table(ut.run(a_tags => 'fast')); ``` -```sql -select * from table(ut.run('hr.test_apply_bonus', a_tags => 'suite1')) +The above call will execute both `ut_sample_test.ut_refcursors1` and `ut_sample_test.ut_test` tests, as both tests are tagged with `fast` + +### Excluding tests/suites by tags + +It is possible to exclude parts of test suites with tags. +In order to do so, prefix the tag name to exclude with a `!` (exclamation) sign when invoking the test run which is equivalent of `-` (dash) in legacy notation. +Examples (based on above sample test suite) + +```sql linenums="1" +select * from table(ut.run(a_tags => '(api|fast)&!complex')); ``` -You can also exclude specific tags by adding a `-` (dash) in front of the tag +or -```sql -select * from table(ut.run('hr.test_apply_bonus', a_tags => '-suite1')) +```sql linenums="1" +select * from table(ut.run(a_tags => '(api|fast)&!complex&!test1')); ``` -# Keeping uncommitted data after test-run +which is equivalent of exclusion on whole expression + +```sql linenums="1" +select * from table(ut.run(a_tags => '(api|fast)&!(complex|test1)')); +``` + +The above calls will execute all suites/contexts/tests that are marked with any of tags `api` or `fast` except those suites/contexts/tests that are marked as `complex` and except those suites/contexts/tests that are marked as `test1`. +Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed. + + +### Sample execution with `any` and `none` + +Given a sample test package: + +```sql linenums="1" +create or replace package ut_sample_test is + + --%suite(Sample Test Suite) + + --%test(Compare Ref Cursors) + --%tags(complex,fast) + procedure ut_refcursors1; + + --%test(Run equality test) + --%tags(simple,fast) + procedure ut_test; + + --%test(Run equality test no tag) + procedure ut_test_no_tag; + +end ut_sample_test; +/ + +create or replace package body ut_sample_test is + + procedure ut_refcursors1 is + v_actual sys_refcursor; + v_expected sys_refcursor; + begin + open v_expected for select 1 as test from dual; + open v_actual for select 2 as test from dual; + + ut.expect(v_actual).to_equal(v_expected); + end; + + procedure ut_test is + begin + ut.expect(1).to_equal(0); + end; + + procedure ut_test_no_tag is + begin + ut.expect(1).to_equal(0); + end; + +end ut_sample_test; +/ +``` + +```sql linenums="1" +select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'none')); +``` + +The above call will execute tests `ut_test_no_tag` + +```sql linenums="1" +select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'any')); +``` + +The above call will execute tests `ut_test` and `ut_refcursors1` + +```sql linenums="1" +select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'none|simple')); +``` + +The above call will execute tests `ut_test_no_tag` and `ut_test` + +```sql linenums="1" +select * from table(ut.run(a_tags => 'none|!simple')); +``` + +The above call will execute tests `ut_test_no_tag` and `ut_refcursors1` + +## Keeping uncommitted data after test-run utPLSQL by default runs tests in autonomous transaction and performs automatic rollback to assure that tests do not impact one-another and do not have impact on the current session in your IDE. @@ -349,7 +544,7 @@ Setting this flag to true has following side-effects: - automatic rollback is forced to be disabled in test-run even if it was explicitly enabled by using annotation `--%rollback(manual) Example invocation: -```sql +```sql linenums="1" set serveroutput on begin ut.run('hr.test_apply_bonus', a_force_manual_rollback => true); @@ -359,7 +554,7 @@ end; **Note:** >This option is not available when running tests using `ut.run` as a table function. -# Reports character-set encoding +## Reports character-set encoding To get properly encoded reports, when running utPLSQL with HTML/XML reports on data containing national characters you need to provide your client character set when calling `ut.run` functions and procedures. @@ -368,8 +563,8 @@ If you run your tests using `utPLSQL-cli`, this is done automatically and no act To make sure that the reports will display your national characters properly when running from IDE like SQLDeveloper/TOAD/SQLPlus or sqlcl you need to provide the charaterset manualy to `ut.run`. Example call with characterset provided: -```sql +```sql linenums="1" begin ut.run('hr.test_apply_bonus', ut_junit_reporter(), a_client_character_set => 'Windows-1251'); end; -``` \ No newline at end of file +``` diff --git a/docs/userguide/upgrade.md b/docs/userguide/upgrade.md index 14e35a5b5..6fdaa6a48 100644 --- a/docs/userguide/upgrade.md +++ b/docs/userguide/upgrade.md @@ -1,4 +1,4 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.1.14.4206--develop-blue.svg) # Upgrading from version 2 diff --git a/examples/developer_examples/RunExampleTestSuiteWithCompositeReporter.sql b/examples/developer_examples/RunExampleTestSuiteWithCompositeReporter.sql index 468d176ed..e713fa02d 100644 --- a/examples/developer_examples/RunExampleTestSuiteWithCompositeReporter.sql +++ b/examples/developer_examples/RunExampleTestSuiteWithCompositeReporter.sql @@ -20,6 +20,7 @@ begin ut_event_manager.initialize(); ut_event_manager.add_listener(l_doc_reporter); ut_event_manager.add_listener(l_tc_reporter); + ut_event_manager.trigger_event(ut_event_manager.gc_initialize, l_run); l_suite := ut_suite(user, 'ut_exampletest',a_line_no=>1); l_suite.description := 'Test Suite Name'; diff --git a/mkdocs.yml b/mkdocs.yml index f9a2687a8..713722620 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,17 +1,90 @@ # Format documented here # http://www.mkdocs.org/user-guide/configuration/ +# https://squidfunk.github.io/mkdocs-material/getting-started/ +# See this document for list of plugins to disable for offline (local) documentation +# https://squidfunk.github.io/mkdocs-material/setup/building-for-offline-usage/ -site_name: utPLSQL -site_description: utPLSQL Ultimate Unit Testing Framework for Oracle PL/SQL -copyright: Copyright © 2016 - 2018 utPLSQL Team +edit_uri: "" +site_url: http://utPLSQL.org/ +site_name: utPLSQL-framework +site_description: utPLSQL Ultimate Testing Framework for Oracle PL/SQL & SQL +copyright: Copyright © 2016 - 2022 utPLSQL Team repo_url: https://github.com/utPLSQL/utPLSQL -theme: mkdocs +extra_css: + - stylesheets/extra.css +theme: + name: material + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/lightbulb-outline + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/lightbulb + name: Switch to light mode + logo: assets/icon-transparent.png + favicon: assets/favicon.png + features: + - navigation.instant # disable for offline docs +# - navigation.indexes + - navigation.tabs + - navigation.tracking + - toc.follow + - toc.integrate + - search.suggest + - search.highlight +extra: +# homepage: http://jgebal.github.io/ +# homepage: http://utPLSQL.org/ + social: + - icon: fontawesome/brands/twitter + link: https://twitter.com/utPLSQL + - icon: fontawesome/brands/slack + link: https://join.slack.com/t/utplsql/shared_invite/zt-xwm68udy-4cF_3PNEyczYEbWr38W5ww + - icon: fontawesome/brands/github + link: https://github.com/utPLSQL + - icon: fontawesome/solid/envelope + link: mailto:utPLSQL@utPLSQL.org + consent: + title: Cookie consent + description: >- + We use cookies to recognize your repeated visits and preferences, as well + as to measure the effectiveness of our documentation and whether users + find what they're searching for. With your consent, you're helping us to + make our documentation better. + version: # disable for offline docs + provider: mike # disable for offline docs +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.caret + - pymdownx.mark + - pymdownx.tilde + - toc: + permalink: true use_directory_urls: false strict: true +plugins: + - search + - mike + - git-revision-date-localized: # disable for offline docs + enable_creation_date: true # disable for offline docs + type: datetime # disable for offline docs + nav: - - Home: index.md - User Guide: + - index.md - Installation: userguide/install.md - Getting Started: userguide/getting-started.md - Annotations: userguide/annotations.md @@ -19,12 +92,12 @@ nav: - Advanced data comparison: userguide/advanced_data_comparison.md - Running unit tests: userguide/running-unit-tests.md - Querying for test suites: userguide/querying_suites.md - - Testing best pracitces: userguide/best-practices.md + - Testing best practices: userguide/best-practices.md - Upgrade utPLSQL: userguide/upgrade.md - Reporting: - Using reporters: userguide/reporters.md - - Reporting errors: userguide/exception-reporting.md - Code coverage: userguide/coverage.md + - Error handling and reporting: userguide/exception-reporting.md - About: - Project Details: about/project-details.md - License: about/license.md diff --git a/mkdocs_offline.yml b/mkdocs_offline.yml new file mode 100644 index 000000000..31c90615e --- /dev/null +++ b/mkdocs_offline.yml @@ -0,0 +1,105 @@ +# Format documented here +# http://www.mkdocs.org/user-guide/configuration/ +# https://squidfunk.github.io/mkdocs-material/getting-started/ +# See this document for list of plugins to disable for offline (local) documentation +# https://squidfunk.github.io/mkdocs-material/setup/building-for-offline-usage/ + +edit_uri: "" +site_url: http://utPLSQL.org/ +site_name: utPLSQL-framework +site_description: utPLSQL Ultimate Testing Framework for Oracle PL/SQL & SQL +copyright: Copyright © 2016 - 2022 utPLSQL Team +#repo_url: https://github.com/utPLSQL/utPLSQL # disable for offline docs +extra_css: + - stylesheets/extra.css +theme: + name: material + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/lightbulb-outline + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/lightbulb + name: Switch to light mode + logo: assets/icon-transparent.png + favicon: assets/favicon.png + features: +# - navigation.instant # disable for offline docs +# - navigation.indexes + - navigation.tabs + - navigation.tracking + - toc.follow + - toc.integrate + - search.suggest + - search.highlight +extra: +# homepage: http://jgebal.github.io/ +# homepage: http://utPLSQL.org/ + social: + - icon: fontawesome/brands/twitter + link: https://twitter.com/utPLSQL + - icon: fontawesome/brands/slack + link: https://join.slack.com/t/utplsql/shared_invite/zt-xwm68udy-4cF_3PNEyczYEbWr38W5ww + - icon: fontawesome/brands/github + link: https://github.com/utPLSQL + - icon: fontawesome/solid/envelope + link: mailto:utPLSQL@utPLSQL.org + consent: + title: Cookie consent + description: >- + We use cookies to recognize your repeated visits and preferences, as well + as to measure the effectiveness of our documentation and whether users + find what they're searching for. With your consent, you're helping us to + make our documentation better. +# version: # disable for offline docs +# provider: mike # disable for offline docs +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.caret + - pymdownx.mark + - pymdownx.tilde + - toc: + permalink: true +use_directory_urls: false +strict: true + +plugins: +# - search # disable for offline docs + - mike +# - git-revision-date-localized: # disable for offline docs +# enable_creation_date: true # disable for offline docs +# type: datetime # disable for offline docs + +nav: + - User Guide: + - index.md + - Installation: userguide/install.md + - Getting Started: userguide/getting-started.md + - Annotations: userguide/annotations.md + - Expectations: userguide/expectations.md + - Advanced data comparison: userguide/advanced_data_comparison.md + - Running unit tests: userguide/running-unit-tests.md + - Querying for test suites: userguide/querying_suites.md + - Testing best practices: userguide/best-practices.md + - Upgrade utPLSQL: userguide/upgrade.md + - Reporting: + - Using reporters: userguide/reporters.md + - Code coverage: userguide/coverage.md + - Error handling and reporting: userguide/exception-reporting.md + - About: + - Project Details: about/project-details.md + - License: about/license.md + - Support: about/support.md + - Authors: about/authors.md diff --git a/sonar-project.properties b/sonar-project.properties index 5d6cefe12..d088e9ccb 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.organization=utplsql sonar.projectKey=utPLSQL # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. sonar.projectName=utPLSQL -sonar.projectVersion=v3.1.13-develop +sonar.projectVersion=v3.1.14-develop # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. # Since SonarQube 4.2, this property is optional if sonar.modules is set. diff --git a/source/api/ut.pkb b/source/api/ut.pkb index 5c91e54d4..cff35b771 100644 --- a/source/api/ut.pkb +++ b/source/api/ut.pkb @@ -222,10 +222,15 @@ create or replace package body ut is raise_if_packages_invalidated(); raise no_data_found; end if; - g_result_lines := ut_utils.clob_to_table(l_clob, ut_utils.gc_max_storage_varchar2_len); - g_result_line_no := g_result_lines.first; + if l_clob is not null and l_clob != empty_clob() then + if length(l_clob) > ut_utils.gc_max_storage_varchar2_len then + g_result_lines := ut_utils.clob_to_table(l_clob, ut_utils.gc_max_storage_varchar2_len); + else + g_result_lines := ut_varchar2_list(l_clob); + end if; + g_result_line_no := g_result_lines.first; + end if; end if; - if g_result_line_no is not null then l_result := g_result_lines(g_result_line_no); g_result_line_no := g_result_lines.next(g_result_line_no); @@ -273,6 +278,7 @@ create or replace package body ut is ); if l_reporter is of (ut_output_reporter_base) then l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + g_result_lines := ut_varchar2_list(); loop pipe row( get_report_outputs( l_results ) ); end loop; @@ -320,6 +326,7 @@ create or replace package body ut is ); if l_reporter is of (ut_output_reporter_base) then l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + g_result_lines := ut_varchar2_list(); loop pipe row( get_report_outputs( l_results ) ); end loop; @@ -368,6 +375,7 @@ create or replace package body ut is ); if l_reporter is of (ut_output_reporter_base) then l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + g_result_lines := ut_varchar2_list(); loop pipe row( get_report_outputs( l_results ) ); end loop; @@ -416,6 +424,7 @@ create or replace package body ut is ); if l_reporter is of (ut_output_reporter_base) then l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + g_result_lines := ut_varchar2_list(); loop pipe row( get_report_outputs( l_results ) ); end loop; @@ -464,6 +473,7 @@ create or replace package body ut is ); if l_reporter is of (ut_output_reporter_base) then l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + g_result_lines := ut_varchar2_list(); loop pipe row( get_report_outputs( l_results ) ); end loop; @@ -512,6 +522,7 @@ create or replace package body ut is ); if l_reporter is of (ut_output_reporter_base) then l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + g_result_lines := ut_varchar2_list(); loop pipe row( get_report_outputs( l_results ) ); end loop; diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index b69a51a04..3ec2a5393 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -78,8 +78,7 @@ create or replace package body ut_runner is l_run ut_run; l_coverage_schema_names ut_varchar2_rows; l_paths ut_varchar2_list; - l_random_test_order_seed positive; - l_tags ut_varchar2_rows := ut_varchar2_rows(); + l_random_test_order_seed positive; begin ut_event_manager.initialize(); if a_reporters is not empty then @@ -118,12 +117,6 @@ create or replace package body ut_runner is l_coverage_schema_names := ut_suite_manager.get_schema_names(l_paths); end if; - - if a_tags is not null then - l_tags := l_tags multiset union distinct ut_utils.convert_collection( - ut_utils.trim_list_elements(ut_utils.filter_list(ut_utils.string_to_table(a_tags,','),ut_utils.gc_word_no_space)) - ); - end if; l_run := ut_run( a_run_paths => l_paths, a_coverage_options => ut_coverage_options( @@ -140,10 +133,10 @@ create or replace package body ut_runner is a_test_file_mappings => set(a_test_file_mappings), a_client_character_set => a_client_character_set, a_random_test_order_seed => l_random_test_order_seed, - a_run_tags => l_tags + a_run_tags => a_tags ); - ut_suite_manager.configure_execution_by_path(l_paths, l_run.items, l_random_test_order_seed, l_tags); + ut_suite_manager.configure_execution_by_path(l_paths, l_run.items, l_random_test_order_seed, a_tags); if a_force_manual_rollback then l_run.set_rollback_type( a_rollback_type => ut_utils.gc_rollback_manual, a_force => true ); end if; diff --git a/source/core/annotations/ut_annotation_manager.pkb b/source/core/annotations/ut_annotation_manager.pkb index 16923bc23..65f7b3e40 100644 --- a/source/core/annotations/ut_annotation_manager.pkb +++ b/source/core/annotations/ut_annotation_manager.pkb @@ -246,7 +246,7 @@ create or replace package body ut_annotation_manager as l_sql_clob := regexp_replace(l_sql_clob, '^(.*?\s*create(\s+or\s+replace)?(\s+(editionable|noneditionable))?\s+?)((package|type).*)', '\5', 1, 1, 'ni'); -- remove "OWNER." from create or replace statement. -- Owner is not supported along with AUTHID - see issue https://github.com/utPLSQL/utPLSQL/issues/1088 - l_sql_clob := regexp_replace(l_sql_clob, '^(package|type)\s+("?[a-zA-Z][a-zA-Z0-9#_$]*"?\.)(.*)', '\1 \3', 1, 1, 'ni'); + l_sql_clob := regexp_replace(l_sql_clob, '^(package|type)\s+("?[[:alpha:]][[:alnum:]$#_]*"?\.)(.*)', '\1 \3', 1, 1, 'ni'); l_sql_lines := ut_utils.convert_collection( ut_utils.clob_to_table(l_sql_clob) ); end if; open l_result for diff --git a/source/core/annotations/ut_annotation_parser.pkb b/source/core/annotations/ut_annotation_parser.pkb index f0271c957..10bb76b3c 100644 --- a/source/core/annotations/ut_annotation_parser.pkb +++ b/source/core/annotations/ut_annotation_parser.pkb @@ -25,7 +25,7 @@ create or replace package body ut_annotation_parser as gc_annot_comment_pattern constant varchar2(30) := '^( |'||chr(09)||')*-- *('||gc_annotation_qualifier||'.*?)$'; -- chr(09) is a tab character gc_comment_replacer_patter constant varchar2(50) := '{COMMENT#%N%}'; gc_comment_replacer_regex_ptrn constant varchar2(25) := '{COMMENT#(\d+)}'; - gc_regexp_identifier constant varchar2(50) := '[a-zA-Z][a-zA-Z0-9#_$]*'; + gc_regexp_identifier constant varchar2(50) := '[[:alpha:]][[:alnum:]$#_]*'; gc_annotation_block_pattern constant varchar2(200) := '(({COMMENT#.+}'||chr(10)||')+)( |'||chr(09)||')*(procedure|function)\s+(' || gc_regexp_identifier || ')'; gc_annotation_pattern constant varchar2(50) := gc_annotation_qualifier || gc_regexp_identifier || '[ '||chr(9)||']*(\(.*?\)\s*?$)?'; diff --git a/source/core/coverage/ut_coverage.pkb b/source/core/coverage/ut_coverage.pkb index f5eccc893..7eb5a34c2 100644 --- a/source/core/coverage/ut_coverage.pkb +++ b/source/core/coverage/ut_coverage.pkb @@ -167,7 +167,7 @@ create or replace package body ut_coverage is begin if not is_develop_mode() then --skip all the utplsql framework objects and all the unit test packages that could potentially be reported by coverage. - l_skip_objects := coalesce(ut_utils.get_utplsql_objects_list(),ut_object_names()); + l_skip_objects := coalesce( ut_utils.get_utplsql_objects_list() multiset union all ut_suite_manager.get_schema_ut_packages(a_coverage_options.schema_names, a_coverage_options.include_schema_expr) , ut_object_names() ); end if; --Regex exclusion override the standard exclusion objects. diff --git a/source/core/coverage/ut_coverage_reporter_base.tpb b/source/core/coverage/ut_coverage_reporter_base.tpb index aff4e6560..da2bd27ad 100644 --- a/source/core/coverage/ut_coverage_reporter_base.tpb +++ b/source/core/coverage/ut_coverage_reporter_base.tpb @@ -92,7 +92,6 @@ create or replace type body ut_coverage_reporter_base is ut_coverage_helper.cleanup_tmp_table(); (l_reporter as ut_output_reporter_base).before_calling_run(null); l_reporter.after_calling_run( ut_run( a_coverage_options => a_coverage_options, a_client_character_set => a_client_character_set ) ); - l_reporter.on_finalize(null); for i in (select /*+ no_parallel */ x.text from table(l_reporter.get_lines(1, 1)) x ) loop pipe row (i.text); end loop; @@ -106,7 +105,6 @@ create or replace type body ut_coverage_reporter_base is ut_coverage_helper.cleanup_tmp_table(); (l_reporter as ut_output_reporter_base).before_calling_run(null); l_reporter.after_calling_run( ut_run( a_coverage_options => a_coverage_options, a_client_character_set => a_client_character_set ) ); - l_reporter.on_finalize(null); open l_result for select /*+ no_parallel */ x.text from table(l_reporter.get_lines(1, 1)) x; return l_result; end; diff --git a/source/core/output_buffers/ut_output_buffer_base.tpb b/source/core/output_buffers/ut_output_buffer_base.tpb index 6d7964150..20cec335e 100644 --- a/source/core/output_buffers/ut_output_buffer_base.tpb +++ b/source/core/output_buffers/ut_output_buffer_base.tpb @@ -24,7 +24,6 @@ create or replace type body ut_output_buffer_base is self.self_type := coalesce(a_self_type,self.self_type); self.output_id := coalesce(a_output_id, self.output_id, sys_guid()); self.start_date := coalesce(self.start_date, sysdate); - self.last_message_id := 0; select /*+ no_parallel */ count(*) into l_exists from ut_output_buffer_info_tmp where output_id = self.output_id; if ( l_exists > 0 ) then update /*+ no_parallel */ ut_output_buffer_info_tmp set start_date = self.start_date where output_id = self.output_id; @@ -32,10 +31,86 @@ create or replace type body ut_output_buffer_base is insert /*+ no_parallel */ into ut_output_buffer_info_tmp(output_id, start_date) values (self.output_id, self.start_date); end if; commit; + dbms_lock.allocate_unique( self.output_id, self.lock_handle); self.is_closed := 0; end; - member function get_lines_cursor(a_initial_timeout natural := null, a_timeout_sec natural := null) return sys_refcursor is + member procedure lock_buffer(a_timeout_sec number := null) is + l_status integer; + begin + l_status := dbms_lock.request( self.lock_handle, dbms_lock.x_mode, 5, false ); + if l_status != 0 then + raise_application_error(-20000, 'Cannot allocate lock for output buffer of reporter. lock request status = '||l_status||', lock handle = '||self.lock_handle||', self.output_id ='||self.output_id); + end if; + end; + + member procedure close(self in out nocopy ut_output_buffer_base) is + l_status integer; + begin + l_status := dbms_lock.release( self.lock_handle ); + if l_status != 0 then + raise_application_error(-20000, 'Cannot release lock for output buffer of reporter. Lock_handle = '||self.lock_handle||' status = '||l_status); + end if; + self.is_closed := 1; + end; + + + member procedure remove_buffer_info(self in ut_output_buffer_base) is + pragma autonomous_transaction; + begin + delete from ut_output_buffer_info_tmp a + where a.output_id = self.output_id; + commit; + end; + + member function timeout_producer_not_started( a_producer_started boolean, a_already_waited_sec number, a_init_wait_sec number ) return boolean + is + l_result boolean := false; + begin + if not a_producer_started and a_already_waited_sec >= a_init_wait_sec then + if a_init_wait_sec > 0 then + self.remove_buffer_info(); + raise_application_error( + ut_utils.gc_out_buffer_timeout, + 'Timeout occurred while waiting for report data producer to start. Waited for: '||ut_utils.to_string( a_already_waited_sec )||' seconds.' + ); + else + l_result := true; + end if; + end if; + return l_result; + end; + + member function timeout_producer_not_finished(a_producer_finished boolean, a_already_waited_sec number, a_timeout_sec number) return boolean + is + l_result boolean := false; + begin + if not a_producer_finished and a_timeout_sec is not null and a_already_waited_sec >= a_timeout_sec then + if a_timeout_sec > 0 then + self.remove_buffer_info(); + raise_application_error( + ut_utils.gc_out_buffer_timeout, + 'Timeout occurred while waiting for more data from producer. Waited for: '||ut_utils.to_string( a_already_waited_sec )||' seconds.' + ); + else + l_result := true; + end if; + end if; + return l_result; + end; + + member function get_lock_status return integer is + l_result integer; + l_release_status integer; + begin + l_result := dbms_lock.request( self.lock_handle, dbms_lock.s_mode, 0, false ); + if l_result = 0 then + l_release_status := dbms_lock.release( self.lock_handle ); + end if; + return l_result; + end; + + member function get_lines_cursor(a_initial_timeout number := null, a_timeout_sec number := null) return sys_refcursor is l_lines sys_refcursor; begin open l_lines for @@ -44,7 +119,7 @@ create or replace type body ut_output_buffer_base is return l_lines; end; - member procedure lines_to_dbms_output(self in ut_output_buffer_base, a_initial_timeout natural := null, a_timeout_sec natural := null) is + member procedure lines_to_dbms_output(self in ut_output_buffer_base, a_initial_timeout number := null, a_timeout_sec number := null) is l_data sys_refcursor; l_clob clob; l_item_type varchar2(32767); @@ -54,16 +129,20 @@ create or replace type body ut_output_buffer_base is loop fetch l_data into l_clob, l_item_type; exit when l_data%notfound; - l_lines := ut_utils.clob_to_table(l_clob); - for i in 1 .. l_lines.count loop - dbms_output.put_line(l_lines(i)); - end loop; + if dbms_lob.getlength(l_clob) > 32767 then + l_lines := ut_utils.clob_to_table(l_clob); + for i in 1 .. l_lines.count loop + dbms_output.put_line(l_lines(i)); + end loop; + else + dbms_output.put_line(l_clob); + end if; end loop; close l_data; end; member procedure cleanup_buffer(self in ut_output_buffer_base, a_retention_time_sec natural := null) is - gc_buffer_retention_sec constant naturaln := coalesce(a_retention_time_sec, 60 * 60 * 24); -- 24 hours + gc_buffer_retention_sec constant naturaln := coalesce(a_retention_time_sec, 60 * 60 * 24 * 5); -- 5 days l_retention_days number := gc_buffer_retention_sec / (60 * 60 * 24); l_max_retention_date date := sysdate - l_retention_days; pragma autonomous_transaction; diff --git a/source/core/output_buffers/ut_output_buffer_base.tps b/source/core/output_buffers/ut_output_buffer_base.tps index 98a6847cd..f2d9ec686 100644 --- a/source/core/output_buffers/ut_output_buffer_base.tps +++ b/source/core/output_buffers/ut_output_buffer_base.tps @@ -19,16 +19,21 @@ create or replace type ut_output_buffer_base force authid definer as object( output_id raw(32), is_closed number(1,0), start_date date, - last_message_id number(38,0), + lock_handle varchar2(30 byte), self_type varchar2(250 byte), member procedure init(self in out nocopy ut_output_buffer_base, a_output_id raw := null, a_self_type varchar2 := null), - member function get_lines_cursor(a_initial_timeout natural := null, a_timeout_sec natural := null) return sys_refcursor, - member procedure lines_to_dbms_output(self in ut_output_buffer_base, a_initial_timeout natural := null, a_timeout_sec natural := null), + member procedure lock_buffer(a_timeout_sec number := null), + member function timeout_producer_not_started( a_producer_started boolean, a_already_waited_sec number, a_init_wait_sec number ) return boolean, + member function timeout_producer_not_finished(a_producer_finished boolean, a_already_waited_sec number, a_timeout_sec number) return boolean, + member function get_lock_status return integer, + member function get_lines_cursor(a_initial_timeout number := null, a_timeout_sec number := null) return sys_refcursor, + member procedure lines_to_dbms_output(self in ut_output_buffer_base, a_initial_timeout number := null, a_timeout_sec number := null), member procedure cleanup_buffer(self in ut_output_buffer_base, a_retention_time_sec natural := null), - not instantiable member procedure close(self in out nocopy ut_output_buffer_base), + member procedure remove_buffer_info(self in ut_output_buffer_base), + member procedure close(self in out nocopy ut_output_buffer_base), not instantiable member procedure send_line(self in out nocopy ut_output_buffer_base, a_text varchar2, a_item_type varchar2 := null), not instantiable member procedure send_lines(self in out nocopy ut_output_buffer_base, a_text_list ut_varchar2_rows, a_item_type varchar2 := null), not instantiable member procedure send_clob(self in out nocopy ut_output_buffer_base, a_text clob, a_item_type varchar2 := null), - not instantiable member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined + not instantiable member function get_lines(a_initial_timeout number := null, a_timeout_sec number := null) return ut_output_data_rows pipelined ) not final not instantiable / diff --git a/source/core/output_buffers/ut_output_buffer_tmp.sql b/source/core/output_buffers/ut_output_buffer_tmp.sql index 57027817e..1ab19e534 100644 --- a/source/core/output_buffers/ut_output_buffer_tmp.sql +++ b/source/core/output_buffers/ut_output_buffer_tmp.sql @@ -30,3 +30,4 @@ create table ut_output_buffer_tmp( ) organization index nologging initrans 100 overflow nologging initrans 100; +create sequence ut_output_buffer_tmp_seq cache 20; diff --git a/source/core/output_buffers/ut_output_bulk_buffer.tpb b/source/core/output_buffers/ut_output_bulk_buffer.tpb new file mode 100644 index 000000000..cfc173e95 --- /dev/null +++ b/source/core/output_buffers/ut_output_bulk_buffer.tpb @@ -0,0 +1,160 @@ +create or replace type body ut_output_bulk_buffer is + /* + utPLSQL - Version 3 + Copyright 2016 - 2023 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + constructor function ut_output_bulk_buffer(self in out nocopy ut_output_bulk_buffer, a_output_id raw := null) return self as result is + begin + self.init(a_output_id, $$plsql_unit); + return; + end; + + overriding member procedure send_line(self in out nocopy ut_output_bulk_buffer, a_text varchar2, a_item_type varchar2 := null) is + pragma autonomous_transaction; + begin + if a_text is not null or a_item_type is not null then + if length(a_text) > ut_utils.gc_max_storage_varchar2_len then + self.send_lines( + ut_utils.convert_collection( + ut_utils.clob_to_table(a_text, ut_utils.gc_max_storage_varchar2_len) + ), + a_item_type + ); + else + insert /*+ no_parallel */ into ut_output_buffer_tmp(output_id, message_id, text, item_type) + values (self.output_id, ut_output_buffer_tmp_seq.nextval, a_text, a_item_type); + end if; + commit; + end if; + end; + + overriding member procedure send_lines(self in out nocopy ut_output_bulk_buffer, a_text_list ut_varchar2_rows, a_item_type varchar2 := null) is + pragma autonomous_transaction; + begin + insert /*+ no_parallel */ into ut_output_buffer_tmp(output_id, message_id, text, item_type) + select /*+ no_parallel */ self.output_id, ut_output_buffer_tmp_seq.nextval, t.column_value, a_item_type + from table(a_text_list) t + where t.column_value is not null or a_item_type is not null; + commit; + end; + + overriding member procedure send_clob(self in out nocopy ut_output_bulk_buffer, a_text clob, a_item_type varchar2 := null) is + pragma autonomous_transaction; + begin + if a_text is not null and a_text != empty_clob() or a_item_type is not null then + if length(a_text) > ut_utils.gc_max_storage_varchar2_len then + self.send_lines( + ut_utils.convert_collection( + ut_utils.clob_to_table(a_text, ut_utils.gc_max_storage_varchar2_len) + ), + a_item_type + ); + else + insert /*+ no_parallel */ into ut_output_buffer_tmp(output_id, message_id, text, item_type) + values (self.output_id, ut_output_buffer_tmp_seq.nextval, a_text, a_item_type); + end if; + commit; + end if; + end; + + overriding member procedure lines_to_dbms_output(self in ut_output_bulk_buffer, a_initial_timeout number := null, a_timeout_sec number := null) is + l_data sys_refcursor; + l_text ut_varchar2_rows; + l_item_type ut_varchar2_rows; + begin + l_data := self.get_lines_cursor(a_initial_timeout, a_timeout_sec); + loop + fetch l_data bulk collect into l_text, l_item_type limit 10000; + for idx in 1 .. l_text.count loop + dbms_output.put_line(l_text(idx)); + end loop; + exit when l_data%notfound; + end loop; + close l_data; + self.remove_buffer_info(); + end; + + overriding member function get_lines_cursor(a_initial_timeout number := null, a_timeout_sec number := null) return sys_refcursor is + lc_init_wait_sec constant number := coalesce(a_initial_timeout, 10 ); + l_already_waited_sec number(10,2) := 0; + l_sleep_time number(2,1); + l_exists integer; + l_finished boolean := false; + l_data_produced boolean := false; + l_producer_active boolean := false; + l_producer_started boolean := false; + l_producer_finished boolean := false; + l_results sys_refcursor; + begin + + while not l_finished loop + + if not l_data_produced then + select /*+ no_parallel */ count(1) into l_exists + from ut_output_buffer_tmp o + where o.output_id = self.output_id and rownum = 1; + l_data_produced := (l_exists = 1); + end if; + + l_sleep_time := case when l_already_waited_sec >= 1 then 0.5 else 0.1 end; + l_producer_active := (self.get_lock_status() <> 0); + l_producer_started := (l_producer_active or l_data_produced ) or l_producer_started; + l_producer_finished := (l_producer_started and not l_producer_active) or l_producer_finished; + l_finished := + self.timeout_producer_not_finished(l_producer_finished, l_already_waited_sec, a_timeout_sec) + or self.timeout_producer_not_started(l_producer_started, l_already_waited_sec, lc_init_wait_sec) + or l_producer_finished; + + dbms_lock.sleep(l_sleep_time); + l_already_waited_sec := l_already_waited_sec + l_sleep_time; + end loop; + + open l_results for + select /*+ no_parallel */ o.text, o.item_type + from ut_output_buffer_tmp o + where o.output_id = self.output_id + and o.text is not null + order by o.output_id, o.message_id; + + return l_results; + + end; + + /* Important note. + This function code is almost duplicated between two types for performance reasons. + The pipe row clause is much faster on VARCHAR2 then it is on clob. + That is the key reason for two implementations. + */ + overriding member function get_lines(a_initial_timeout number := null, a_timeout_sec number := null) return ut_output_data_rows pipelined is + l_data sys_refcursor; + l_text ut_varchar2_rows; + l_item_type ut_varchar2_rows; + begin + l_data := self.get_lines_cursor(a_initial_timeout, a_timeout_sec); + loop + fetch l_data bulk collect into l_text, l_item_type limit 10000; + for idx in 1 .. l_text.count loop + pipe row( ut_output_data_row(l_text(idx), l_item_type(idx)) ); + end loop; + exit when l_data%notfound; + end loop; + close l_data; + self.remove_buffer_info(); + return; + end; + +end; +/ diff --git a/source/core/output_buffers/ut_output_bulk_buffer.tps b/source/core/output_buffers/ut_output_bulk_buffer.tps new file mode 100644 index 000000000..d74d4ee14 --- /dev/null +++ b/source/core/output_buffers/ut_output_bulk_buffer.tps @@ -0,0 +1,27 @@ +create or replace type ut_output_bulk_buffer under ut_output_buffer_base ( + /* + utPLSQL - Version 3 + Copyright 2016 - 2023 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + constructor function ut_output_bulk_buffer(self in out nocopy ut_output_bulk_buffer, a_output_id raw := null) return self as result, + overriding member procedure send_line(self in out nocopy ut_output_bulk_buffer, a_text varchar2, a_item_type varchar2 := null), + overriding member procedure send_lines(self in out nocopy ut_output_bulk_buffer, a_text_list ut_varchar2_rows, a_item_type varchar2 := null), + overriding member procedure send_clob(self in out nocopy ut_output_bulk_buffer, a_text clob, a_item_type varchar2 := null), + overriding member procedure lines_to_dbms_output(self in ut_output_bulk_buffer, a_initial_timeout number := null, a_timeout_sec number := null), + overriding member function get_lines_cursor(a_initial_timeout number := null, a_timeout_sec number := null) return sys_refcursor, + overriding member function get_lines(a_initial_timeout number := null, a_timeout_sec number := null) return ut_output_data_rows pipelined +) not final +/ diff --git a/source/core/output_buffers/ut_output_clob_buffer_tmp.sql b/source/core/output_buffers/ut_output_clob_buffer_tmp.sql index 40dda121f..9ff9f7413 100644 --- a/source/core/output_buffers/ut_output_clob_buffer_tmp.sql +++ b/source/core/output_buffers/ut_output_clob_buffer_tmp.sql @@ -45,3 +45,5 @@ begin end; end; / + +create sequence ut_output_clob_buffer_tmp_seq cache 20; diff --git a/source/core/output_buffers/ut_output_clob_table_buffer.tpb b/source/core/output_buffers/ut_output_clob_table_buffer.tpb index 66ff71c62..3d49ab08d 100644 --- a/source/core/output_buffers/ut_output_clob_table_buffer.tpb +++ b/source/core/output_buffers/ut_output_clob_table_buffer.tpb @@ -22,23 +22,12 @@ create or replace type body ut_output_clob_table_buffer is return; end; - overriding member procedure close(self in out nocopy ut_output_clob_table_buffer) is - pragma autonomous_transaction; - begin - self.last_message_id := self.last_message_id + 1; - insert /*+ no_parallel */ into ut_output_clob_buffer_tmp(output_id, message_id, is_finished) - values (self.output_id, self.last_message_id, 1); - commit; - self.is_closed := 1; - end; - overriding member procedure send_line(self in out nocopy ut_output_clob_table_buffer, a_text varchar2, a_item_type varchar2 := null) is pragma autonomous_transaction; begin if a_text is not null or a_item_type is not null then - self.last_message_id := self.last_message_id + 1; insert /*+ no_parallel */ into ut_output_clob_buffer_tmp(output_id, message_id, text, item_type) - values (self.output_id, self.last_message_id, a_text, a_item_type); + values (self.output_id, ut_output_clob_buffer_tmp_seq.nextval, a_text, a_item_type); end if; commit; end; @@ -47,10 +36,9 @@ create or replace type body ut_output_clob_table_buffer is pragma autonomous_transaction; begin insert /*+ no_parallel */ into ut_output_clob_buffer_tmp(output_id, message_id, text, item_type) - select /*+ no_parallel */ self.output_id, self.last_message_id + rownum, t.column_value, a_item_type + select /*+ no_parallel */ self.output_id, ut_output_clob_buffer_tmp_seq.nextval, t.column_value, a_item_type from table(a_text_list) t where t.column_value is not null or a_item_type is not null; - self.last_message_id := self.last_message_id + SQL%rowcount; commit; end; @@ -58,98 +46,87 @@ create or replace type body ut_output_clob_table_buffer is pragma autonomous_transaction; begin if a_text is not null and a_text != empty_clob() or a_item_type is not null then - self.last_message_id := self.last_message_id + 1; insert /*+ no_parallel */ into ut_output_clob_buffer_tmp(output_id, message_id, text, item_type) - values (self.output_id, self.last_message_id, a_text, a_item_type); + values (self.output_id, ut_output_clob_buffer_tmp_seq.nextval, a_text, a_item_type); end if; commit; end; - overriding member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined is - type t_rowid_tab is table of urowid; - l_message_rowids t_rowid_tab; - l_buffer_data ut_output_data_rows; - l_finished_flags ut_integer_list; - l_already_waited_for number(10,2) := 0; - l_finished boolean := false; - lc_init_wait_sec constant naturaln := coalesce(a_initial_timeout, 60 ); -- 1 minute - lc_max_wait_sec constant naturaln := coalesce(a_timeout_sec, 60 * 60 * 4); -- 4 hours - l_wait_for integer := lc_init_wait_sec; - lc_short_sleep_time constant number(1,1) := 0.1; --sleep for 100 ms between checks - lc_long_sleep_time constant number(1) := 1; --sleep for 1 s when waiting long - lc_long_wait_time constant number(1) := 1; --waiting more than 1 sec - l_sleep_time number(2,1) := lc_short_sleep_time; - lc_bulk_limit constant integer := 5000; - l_max_message_id integer := lc_bulk_limit; - - procedure remove_read_data(a_message_rowids t_rowid_tab) is - pragma autonomous_transaction; - begin - forall i in 1 .. a_message_rowids.count - delete from ut_output_clob_buffer_tmp a - where rowid = a_message_rowids(i); - commit; - end; - - procedure remove_buffer_info is - pragma autonomous_transaction; - begin - delete from ut_output_buffer_info_tmp a - where a.output_id = self.output_id; - commit; - end; - + overriding member function get_lines(a_initial_timeout number := null, a_timeout_sec number := null) return ut_output_data_rows pipelined is + lc_init_wait_sec constant number := coalesce(a_initial_timeout, 10 ); + l_buffer_rowids ut_varchar2_rows; + l_buffer_data ut_output_data_rows; + l_finished_flags ut_integer_list; + l_already_waited_sec number(10,2) := 0; + l_finished boolean := false; + l_sleep_time number(2,1); + l_lock_status integer; + l_producer_started boolean := false; + l_producer_finished boolean := false; + procedure get_data_from_buffer_table( + a_buffer_data out nocopy ut_output_data_rows, + a_buffer_rowids out nocopy ut_varchar2_rows, + a_finished_flags out nocopy ut_integer_list + ) is + lc_bulk_limit constant integer := 5000; begin - while not l_finished loop with ordered_buffer as ( - select /*+ no_parallel index(a) */ a.rowid, ut_output_data_row(a.text, a.item_type), is_finished + select /*+ no_parallel index(a) */ ut_output_data_row(a.text, a.item_type), rowidtochar(a.rowid), is_finished from ut_output_clob_buffer_tmp a where a.output_id = self.output_id - and a.message_id <= l_max_message_id + and a.message_id <= (select min(message_id) from ut_output_clob_buffer_tmp o where o.output_id = self.output_id) + lc_bulk_limit order by a.message_id ) select /*+ no_parallel */ b.* - bulk collect into l_message_rowids, l_buffer_data, l_finished_flags + bulk collect into a_buffer_data, a_buffer_rowids, a_finished_flags from ordered_buffer b; + end; - --nothing fetched from output, wait and try again - if l_buffer_data.count = 0 then - $if dbms_db_version.version >= 18 $then - dbms_session.sleep(l_sleep_time); - $else - dbms_lock.sleep(l_sleep_time); - $end - l_already_waited_for := l_already_waited_for + l_sleep_time; - if l_already_waited_for > lc_long_wait_time then - l_sleep_time := lc_long_sleep_time; - end if; - else - --reset wait time - -- we wait lc_max_wait_sec for new message - l_wait_for := lc_max_wait_sec; - l_already_waited_for := 0; - l_sleep_time := lc_short_sleep_time; + procedure remove_read_data(a_buffer_rowids ut_varchar2_rows) is + pragma autonomous_transaction; + begin + forall i in 1 .. a_buffer_rowids.count + delete from ut_output_clob_buffer_tmp a + where rowid = chartorowid(a_buffer_rowids(i)); + commit; + end; + + begin + while not l_finished loop + + l_sleep_time := case when l_already_waited_sec >= 1 then 0.5 else 0.1 end; + l_lock_status := self.get_lock_status(); + get_data_from_buffer_table( l_buffer_data, l_buffer_rowids, l_finished_flags ); + + if l_buffer_data.count > 0 then + l_already_waited_sec := 0; for i in 1 .. l_buffer_data.count loop if l_buffer_data(i).text is not null then - pipe row(l_buffer_data(i)); + pipe row( l_buffer_data(i) ); elsif l_finished_flags(i) = 1 then l_finished := true; exit; end if; end loop; - remove_read_data(l_message_rowids); - l_max_message_id := l_max_message_id + lc_bulk_limit; - end if; - if l_finished or l_already_waited_for >= l_wait_for then - remove_buffer_info(); - if l_already_waited_for > 0 and l_already_waited_for >= l_wait_for then - raise_application_error( - ut_utils.gc_out_buffer_timeout, - 'Timeout occurred while waiting for output data. Waited for: '||l_already_waited_for||' seconds.' - ); - end if; + remove_read_data(l_buffer_rowids); + else + --nothing fetched from output, wait. + dbms_lock.sleep(l_sleep_time); + l_already_waited_sec := l_already_waited_sec + l_sleep_time; end if; + + l_producer_started := (l_lock_status <> 0 or l_buffer_data.count > 0) or l_producer_started; + l_producer_finished := (l_producer_started and l_lock_status = 0 and l_buffer_data.count = 0) or l_producer_finished; + + l_finished := + self.timeout_producer_not_finished(l_producer_finished, l_already_waited_sec, a_timeout_sec) + or self.timeout_producer_not_started(l_producer_started, l_already_waited_sec, lc_init_wait_sec) + or l_producer_finished + or l_finished; + end loop; + + self.remove_buffer_info(); return; end; diff --git a/source/core/output_buffers/ut_output_clob_table_buffer.tps b/source/core/output_buffers/ut_output_clob_table_buffer.tps index 7b98efaba..191e64c01 100644 --- a/source/core/output_buffers/ut_output_clob_table_buffer.tps +++ b/source/core/output_buffers/ut_output_clob_table_buffer.tps @@ -20,7 +20,6 @@ create or replace type ut_output_clob_table_buffer under ut_output_buffer_base ( overriding member procedure send_line(self in out nocopy ut_output_clob_table_buffer, a_text varchar2, a_item_type varchar2 := null), overriding member procedure send_lines(self in out nocopy ut_output_clob_table_buffer, a_text_list ut_varchar2_rows, a_item_type varchar2 := null), overriding member procedure send_clob(self in out nocopy ut_output_clob_table_buffer, a_text clob, a_item_type varchar2 := null), - overriding member procedure close(self in out nocopy ut_output_clob_table_buffer), - overriding member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined + overriding member function get_lines(a_initial_timeout number := null, a_timeout_sec number := null) return ut_output_data_rows pipelined ) not final / diff --git a/source/core/output_buffers/ut_output_table_buffer.tpb b/source/core/output_buffers/ut_output_table_buffer.tpb index 1809a49d5..e8e2442a7 100644 --- a/source/core/output_buffers/ut_output_table_buffer.tpb +++ b/source/core/output_buffers/ut_output_table_buffer.tpb @@ -22,21 +22,11 @@ create or replace type body ut_output_table_buffer is return; end; - overriding member procedure close(self in out nocopy ut_output_table_buffer) is - pragma autonomous_transaction; - begin - self.last_message_id := self.last_message_id + 1; - insert /*+ no_parallel */ into ut_output_buffer_tmp(output_id, message_id, is_finished) - values (self.output_id, self.last_message_id, 1); - commit; - self.is_closed := 1; - end; - overriding member procedure send_line(self in out nocopy ut_output_table_buffer, a_text varchar2, a_item_type varchar2 := null) is pragma autonomous_transaction; begin if a_text is not null or a_item_type is not null then - if length(a_text) > ut_utils.gc_max_storage_varchar2_len then + if lengthb(a_text) > ut_utils.gc_max_storage_varchar2_len then self.send_lines( ut_utils.convert_collection( ut_utils.clob_to_table(a_text, ut_utils.gc_max_storage_varchar2_len) @@ -44,9 +34,8 @@ create or replace type body ut_output_table_buffer is a_item_type ); else - self.last_message_id := self.last_message_id + 1; insert /*+ no_parallel */ into ut_output_buffer_tmp(output_id, message_id, text, item_type) - values (self.output_id, self.last_message_id, a_text, a_item_type); + values (self.output_id, ut_output_buffer_tmp_seq.nextval, a_text, a_item_type); end if; commit; end if; @@ -56,10 +45,9 @@ create or replace type body ut_output_table_buffer is pragma autonomous_transaction; begin insert /*+ no_parallel */ into ut_output_buffer_tmp(output_id, message_id, text, item_type) - select /*+ no_parallel */ self.output_id, self.last_message_id + rownum, t.column_value, a_item_type + select /*+ no_parallel */ self.output_id, ut_output_buffer_tmp_seq.nextval, t.column_value, a_item_type from table(a_text_list) t where t.column_value is not null or a_item_type is not null; - self.last_message_id := self.last_message_id + SQL%rowcount; commit; end; @@ -67,7 +55,7 @@ create or replace type body ut_output_table_buffer is pragma autonomous_transaction; begin if a_text is not null and a_text != empty_clob() or a_item_type is not null then - if length(a_text) > ut_utils.gc_max_storage_varchar2_len then + if ut_utils.lengthb_clob(a_text) > ut_utils.gc_max_storage_varchar2_len then self.send_lines( ut_utils.convert_collection( ut_utils.clob_to_table(a_text, ut_utils.gc_max_storage_varchar2_len) @@ -75,99 +63,100 @@ create or replace type body ut_output_table_buffer is a_item_type ); else - self.last_message_id := self.last_message_id + 1; insert /*+ no_parallel */ into ut_output_buffer_tmp(output_id, message_id, text, item_type) - values (self.output_id, self.last_message_id, a_text, a_item_type); + values (self.output_id, ut_output_buffer_tmp_seq.nextval, a_text, a_item_type); end if; commit; end if; end; - overriding member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined is - l_buffer_data ut_varchar2_rows; - l_item_types ut_varchar2_rows; - l_finished_flags ut_integer_list; - l_already_waited_for number(10,2) := 0; - l_finished boolean := false; - lc_init_wait_sec constant naturaln := coalesce(a_initial_timeout, 60 ); -- 1 minute - lc_max_wait_sec constant naturaln := coalesce(a_timeout_sec, 60 * 60 * 4); -- 4 hours - l_wait_for integer := lc_init_wait_sec; - lc_short_sleep_time constant number(1,1) := 0.1; --sleep for 100 ms between checks - lc_long_sleep_time constant number(1) := 1; --sleep for 1 s when waiting long - lc_long_wait_time constant number(1) := 1; --waiting more than 1 sec - l_sleep_time number(2,1) := lc_short_sleep_time; - lc_bulk_limit constant integer := 5000; - l_max_message_id integer := lc_bulk_limit; - - procedure get_data_from_buffer( - a_max_message_id integer, - a_buffer_data out nocopy ut_varchar2_rows, - a_item_types out nocopy ut_varchar2_rows, - a_finished_flags out nocopy ut_integer_list - ) is - pragma autonomous_transaction; - begin - delete /*+ no_parallel */ from ( - select /*+ no_parallel */ * - from ut_output_buffer_tmp o - where o.output_id = self.output_id - and o.message_id <= a_max_message_id - order by o.message_id - ) d - returning d.text, d.item_type, d.is_finished - bulk collect into a_buffer_data, a_item_types, a_finished_flags; - commit; - - end; - - procedure remove_buffer_info is - pragma autonomous_transaction; - begin - delete from ut_output_buffer_info_tmp a - where a.output_id = self.output_id; - commit; - end; + overriding member procedure lines_to_dbms_output(self in ut_output_table_buffer, a_initial_timeout number := null, a_timeout_sec number := null) is + l_data sys_refcursor; + l_text ut_varchar2_rows; + l_item_type ut_varchar2_rows; + begin + l_data := self.get_lines_cursor(a_initial_timeout, a_timeout_sec); + loop + fetch l_data bulk collect into l_text, l_item_type limit 10000; + for idx in 1 .. l_text.count loop + dbms_output.put_line(l_text(idx)); + end loop; + exit when l_data%notfound; + end loop; + close l_data; + end; - begin + /* Important note. + This function code is almost duplicated between two types for performance reasons. + The pipe row clause is much faster on VARCHAR2 then it is on clob. + That is the key reason for two implementations. + */ + overriding member function get_lines(a_initial_timeout number := null, a_timeout_sec number := null) return ut_output_data_rows pipelined is + lc_init_wait_sec constant number := coalesce(a_initial_timeout, 10 ); + l_buffer_texts ut_varchar2_rows; + l_buffer_item_types ut_varchar2_rows; + l_finished_flags ut_integer_list; + l_already_waited_sec number(10,2) := 0; + l_finished boolean := false; + l_sleep_time number(2,1); + l_lock_status integer; + l_producer_started boolean := false; + l_producer_finished boolean := false; + + procedure get_data_from_buffer_table( + a_buffer_texts out nocopy ut_varchar2_rows, + a_buffer_item_types out nocopy ut_varchar2_rows, + a_finished_flags out nocopy ut_integer_list + ) is + lc_bulk_limit constant integer := 20000; + pragma autonomous_transaction; + begin + delete /*+ no_parallel */ from ( + select /*+ no_parallel */ * + from ut_output_buffer_tmp a + where a.output_id = self.output_id + and a.message_id <= (select min(message_id) from ut_output_buffer_tmp o where o.output_id = self.output_id) + lc_bulk_limit + order by a.message_id + ) d + returning d.text, d.item_type, d.is_finished + bulk collect into a_buffer_texts, a_buffer_item_types, a_finished_flags; + commit; + end; + begin while not l_finished loop - get_data_from_buffer( l_max_message_id, l_buffer_data, l_item_types, l_finished_flags); - --nothing fetched from output, wait and try again - if l_buffer_data.count = 0 then - $if dbms_db_version.version >= 18 $then - dbms_session.sleep(l_sleep_time); - $else - dbms_lock.sleep(l_sleep_time); - $end - l_already_waited_for := l_already_waited_for + l_sleep_time; - if l_already_waited_for > lc_long_wait_time then - l_sleep_time := lc_long_sleep_time; - end if; - else - --reset wait time - -- we wait lc_max_wait_sec for new message - l_wait_for := lc_max_wait_sec; - l_already_waited_for := 0; - l_sleep_time := lc_short_sleep_time; - for i in 1 .. l_buffer_data.count loop - if l_buffer_data(i) is not null then - pipe row(ut_output_data_row(l_buffer_data(i),l_item_types(i))); + + l_sleep_time := case when l_already_waited_sec >= 1 then 0.5 else 0.1 end; + l_lock_status := self.get_lock_status(); + get_data_from_buffer_table( l_buffer_texts, l_buffer_item_types, l_finished_flags ); + + if l_buffer_texts.count > 0 then + l_already_waited_sec := 0; + for i in 1 .. l_buffer_texts.count loop + if l_buffer_texts(i) is not null then + pipe row( ut_output_data_row(l_buffer_texts(i), l_buffer_item_types(i)) ); elsif l_finished_flags(i) = 1 then l_finished := true; exit; end if; end loop; - l_max_message_id := l_max_message_id + lc_bulk_limit; - end if; - if l_finished or l_already_waited_for >= l_wait_for then - remove_buffer_info(); - if l_already_waited_for > 0 and l_already_waited_for >= l_wait_for then - raise_application_error( - ut_utils.gc_out_buffer_timeout, - 'Timeout occurred while waiting for output data. Waited for: '||l_already_waited_for||' seconds.' - ); - end if; + else + --nothing fetched from output, wait. + dbms_lock.sleep(l_sleep_time); + l_already_waited_sec := l_already_waited_sec + l_sleep_time; end if; + + l_producer_started := (l_lock_status <> 0 or l_buffer_texts.count > 0) or l_producer_started; + l_producer_finished := (l_producer_started and l_lock_status = 0 and l_buffer_texts.count = 0) or l_producer_finished; + + l_finished := + self.timeout_producer_not_finished(l_producer_finished, l_already_waited_sec, a_timeout_sec) + or self.timeout_producer_not_started(l_producer_started, l_already_waited_sec, lc_init_wait_sec) + or l_producer_finished + or l_finished; + end loop; + + self.remove_buffer_info(); return; end; diff --git a/source/core/output_buffers/ut_output_table_buffer.tps b/source/core/output_buffers/ut_output_table_buffer.tps index 726b692f8..154ce4de6 100644 --- a/source/core/output_buffers/ut_output_table_buffer.tps +++ b/source/core/output_buffers/ut_output_table_buffer.tps @@ -20,7 +20,7 @@ create or replace type ut_output_table_buffer under ut_output_buffer_base ( overriding member procedure send_line(self in out nocopy ut_output_table_buffer, a_text varchar2, a_item_type varchar2 := null), overriding member procedure send_lines(self in out nocopy ut_output_table_buffer, a_text_list ut_varchar2_rows, a_item_type varchar2 := null), overriding member procedure send_clob(self in out nocopy ut_output_table_buffer, a_text clob, a_item_type varchar2 := null), - overriding member procedure close(self in out nocopy ut_output_table_buffer), - overriding member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined + overriding member procedure lines_to_dbms_output(self in ut_output_table_buffer, a_initial_timeout number := null, a_timeout_sec number := null), + overriding member function get_lines(a_initial_timeout number := null, a_timeout_sec number := null) return ut_output_data_rows pipelined ) not final / diff --git a/source/core/types/ut_executable_test.tpb b/source/core/types/ut_executable_test.tpb index c5a7f29a1..fa5872e04 100644 --- a/source/core/types/ut_executable_test.tpb +++ b/source/core/types/ut_executable_test.tpb @@ -159,7 +159,7 @@ create or replace type body ut_executable_test as if self.error_stack is null then l_fail_message := 'Expected one of exceptions ('||l_expected_error_codes||') but nothing was raised.'; else - l_actual_error_no := regexp_substr(self.error_stack, '^[a-zA-Z]{3}(-[0-9]+)', subexpression=>1); + l_actual_error_no := regexp_substr(self.error_stack, '^[[:alpha:]]{3}(-[0-9]+)', subexpression=>1); if not l_actual_error_no member of a_expected_error_codes or l_actual_error_no is null then l_fail_message := 'Actual: '||l_actual_error_no||' was expected to '; if cardinality(a_expected_error_codes) > 1 then diff --git a/source/core/types/ut_output_reporter_base.tpb b/source/core/types/ut_output_reporter_base.tpb index f6bb27b94..48970be5a 100644 --- a/source/core/types/ut_output_reporter_base.tpb +++ b/source/core/types/ut_output_reporter_base.tpb @@ -41,13 +41,6 @@ create or replace type body ut_output_reporter_base is return l_result; end; - overriding member procedure before_calling_run(self in out nocopy ut_output_reporter_base, a_run in ut_run) is - l_output_table_buffer ut_output_table_buffer; - begin - (self as ut_reporter_base).before_calling_run(a_run); - l_output_table_buffer := treat(self.output_buffer as ut_output_table_buffer); - end; - member procedure print_text(self in out nocopy ut_output_reporter_base, a_text varchar2, a_item_type varchar2 := null) is begin self.output_buffer.send_line(a_text, a_item_type); @@ -87,6 +80,7 @@ create or replace type body ut_output_reporter_base is overriding member procedure on_initialize(self in out nocopy ut_output_reporter_base, a_run in ut_run) is begin + self.output_buffer.lock_buffer(); self.output_buffer.send_line(null, 'initialize'); end; diff --git a/source/core/types/ut_output_reporter_base.tps b/source/core/types/ut_output_reporter_base.tps index 22f507f8d..21eed9957 100644 --- a/source/core/types/ut_output_reporter_base.tps +++ b/source/core/types/ut_output_reporter_base.tps @@ -20,8 +20,7 @@ create or replace type ut_output_reporter_base under ut_reporter_base( member procedure init(self in out nocopy ut_output_reporter_base, a_self_type varchar2, a_output_buffer ut_output_buffer_base := null), overriding member procedure set_reporter_id(self in out nocopy ut_output_reporter_base, a_reporter_id raw), member function set_reporter_id(self in ut_output_reporter_base, a_reporter_id raw) return ut_output_reporter_base, - overriding member procedure before_calling_run(self in out nocopy ut_output_reporter_base, a_run in ut_run), - + member procedure print_text(self in out nocopy ut_output_reporter_base, a_text varchar2, a_item_type varchar2 := null), member procedure print_text_lines(self in out nocopy ut_output_reporter_base, a_text_lines ut_varchar2_rows, a_item_type varchar2 := null), member procedure print_clob(self in out nocopy ut_output_reporter_base, a_clob clob, a_item_type varchar2 := null), diff --git a/source/core/types/ut_run.tpb b/source/core/types/ut_run.tpb index cdafb30fc..660c88791 100644 --- a/source/core/types/ut_run.tpb +++ b/source/core/types/ut_run.tpb @@ -24,7 +24,7 @@ create or replace type body ut_run as a_test_file_mappings ut_file_mappings := null, a_client_character_set varchar2 := null, a_random_test_order_seed positive := null, - a_run_tags ut_varchar2_rows := null + a_run_tags varchar2 := null ) return self as result is begin self.run_paths := a_run_paths; diff --git a/source/core/types/ut_run.tps b/source/core/types/ut_run.tps index debf847cd..1878a2d46 100644 --- a/source/core/types/ut_run.tps +++ b/source/core/types/ut_run.tps @@ -21,7 +21,7 @@ create or replace type ut_run under ut_suite_item ( project_name varchar2(4000), items ut_suite_items, run_paths ut_varchar2_list, - run_tags ut_varchar2_rows, + run_tags varchar2(4000), coverage_options ut_coverage_options, test_file_mappings ut_file_mappings, client_character_set varchar2(100), @@ -34,7 +34,7 @@ create or replace type ut_run under ut_suite_item ( a_test_file_mappings ut_file_mappings := null, a_client_character_set varchar2 := null, a_random_test_order_seed positive := null, - a_run_tags ut_varchar2_rows := null + a_run_tags varchar2 := null ) return self as result, overriding member procedure mark_as_skipped(self in out nocopy ut_run,a_skip_reason in varchar2), overriding member function do_execute(self in out nocopy ut_run) return boolean, diff --git a/source/core/types/ut_stack.tpb b/source/core/types/ut_stack.tpb new file mode 100644 index 000000000..b5f4e8747 --- /dev/null +++ b/source/core/types/ut_stack.tpb @@ -0,0 +1,58 @@ +create or replace type body ut_stack as + /* + utPLSQL - Version 3 + Copyright 2016 - 2023 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + constructor function ut_stack( self in out nocopy ut_stack) return self as result is + begin + self.tokens := ut_varchar2_list(); + self.top := 0; + return; + end ut_stack; + + member function peek(self in out nocopy ut_stack) return varchar2 is + l_token varchar2(32767); + begin + if self.tokens.count =0 or self.tokens is null then + l_token := null; + else + l_token := self.tokens(self.tokens.last); + end if; + return l_token; + end; + + member procedure push(self in out nocopy ut_stack, a_token varchar2) is + begin + self.tokens.extend; + self.tokens(self.tokens.last) := a_token; + self.top := self.tokens.count; + end push; + + member procedure pop(self in out nocopy ut_stack,a_cnt in integer default 1) is + begin + self.tokens.trim(a_cnt); + self.top := self.tokens.count; + end pop; + + member function pop(self in out nocopy ut_stack) return varchar2 is + l_token varchar2(32767) := self.tokens(self.tokens.last); + begin + self.pop(); + return l_token; + end; +end; +/ + diff --git a/source/core/types/ut_stack.tps b/source/core/types/ut_stack.tps new file mode 100644 index 000000000..23112fdde --- /dev/null +++ b/source/core/types/ut_stack.tps @@ -0,0 +1,26 @@ +create or replace type ut_stack as object ( + top integer, + tokens ut_varchar2_list, + /* + utPLSQL - Version 3 + Copyright 2016 - 2023 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + constructor function ut_stack( self in out nocopy ut_stack) return self as result, + member function peek(self in out nocopy ut_stack) return varchar2, + member procedure push(self in out nocopy ut_stack, a_token varchar2), + member procedure pop(self in out nocopy ut_stack,a_cnt in integer default 1), + member function pop(self in out nocopy ut_stack) return varchar2 +) +/ \ No newline at end of file diff --git a/source/core/ut_expectation_processor.pkb b/source/core/ut_expectation_processor.pkb index 77b734b30..c165a9ee5 100644 --- a/source/core/ut_expectation_processor.pkb +++ b/source/core/ut_expectation_processor.pkb @@ -159,17 +159,17 @@ create or replace package body ut_expectation_processor as -- when 11g and 12c reports only package name function cut_header_and_expectations( a_stack varchar2 ) return varchar2 is begin - return regexp_substr( a_stack, '(.*\.(UT_EQUAL|UT_BE_WITHIN[A-Z0-9#_$]*|UT_EXPECTATION[A-Z0-9#_$]*|UT|UTASSERT2?)(\.[A-Z0-9#_$]+)?\s+)+((.|\s)*)', 1, 1, 'm', 4); + return regexp_substr( a_stack, '(.*\.(UT_EQUAL|UT_BE_WITHIN[[:alnum:]$#_]*|UT_EXPECTATION[[:alnum:]$#_]*|UT|UTASSERT2?)(\.[[:alnum:]$#_]+)?\s+)+((.|\s)*)', 1, 1, 'm', 4); end; function cut_address_columns( a_stack varchar2 ) return varchar2 is begin - return regexp_replace( a_stack, '^(0x)?[0-9a-f]+\s+', '', 1, 0, 'mi' ); + return regexp_replace( a_stack, '^(0x)?[[:digit:]abcdef]+\s+', '', 1, 0, 'mi' ); end; function cut_framework_stack( a_stack varchar2 ) return varchar2 is begin return regexp_replace( a_stack, - '[0-9]+\s+anonymous\s+block\s+[0-9]+\s+package\s+body\s+sys\.dbms_sql(\.execute)?\s+[0-9]+\s+[0-9_$#a-z ]+\.ut_executable.*', + '[0-9]+\s+anonymous\s+block\s+[0-9]+\s+package\s+body\s+sys\.dbms_sql(\.execute)?\s+[0-9]+\s+[[:alnum:]_$# ]+\.ut_executable.*', '', 1, 1, 'mni' ); @@ -178,7 +178,7 @@ create or replace package body ut_expectation_processor as begin return regexp_replace( a_stack, - '([0-9]+)\s+(.* )?((anonymous block)|(([0-9_$#a-z]+\.[0-9_$#a-z]+(\.([0-9_$#a-z])+)?)))', + '([0-9]+)\s+(.* )?((anonymous block)|(([[:alnum:]$#_]+\.[[:alnum:]$#_]+(\.([[:alnum:]$#_])+)?)))', 'at "\3", line \1', 1, 0, 'i' ); end; @@ -190,8 +190,8 @@ create or replace package body ut_expectation_processor as l_caller_stack_line := regexp_substr(l_call_stack,'^(.*)'); if l_caller_stack_line like '%.%' then l_line_no := to_number( regexp_substr( l_caller_stack_line, ', line (\d+)', subexpression => 1 ) ); - l_owner := regexp_substr( l_caller_stack_line, 'at "([A-Za-z0-9$#_]+)\.(([A-Za-z0-9$#_]+)(\.([A-Za-z0-9$#_]+))?)", line (\d+)', subexpression => 1 ); - l_object_name := regexp_substr( l_caller_stack_line, 'at "([A-Za-z0-9$#_]+)\.(([A-Za-z0-9$#_]+)(\.([A-Za-z0-9$#_]+))?)", line (\d+)', subexpression => 3 ); + l_owner := regexp_substr( l_caller_stack_line, 'at "([[:alnum:]$#_]+)\.(([[:alnum:]$#_]+)(\.([[:alnum:]$#_]+))?)", line (\d+)', subexpression => 1 ); + l_object_name := regexp_substr( l_caller_stack_line, 'at "([[:alnum:]$#_]+)\.(([[:alnum:]$#_]+)(\.([[:alnum:]$#_]+))?)", line (\d+)', subexpression => 3 ); l_result := l_caller_stack_line || ' ' || rtrim(ut_metadata.get_source_definition_line(l_owner, l_object_name, l_line_no),chr(10)) || replace( l_call_stack, l_caller_stack_line ); diff --git a/source/core/ut_metadata.pkb b/source/core/ut_metadata.pkb index 700c0efb0..360b7b97d 100644 --- a/source/core/ut_metadata.pkb +++ b/source/core/ut_metadata.pkb @@ -304,7 +304,7 @@ create or replace package body ut_metadata as begin l_result := regexp_substr( a_full_object_name, - '^([A-Za-z0-9$#_]+|".*?")\.([A-Za-z0-9$#_]+|".*?")', subexpression => 2 + '^([[:alnum:]$#_]+|".*?")\.([[:alnum:]$#_]+|".*?")', subexpression => 2 ); if not l_result like '"%"' then l_result := upper(l_result); diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index d3e832a9b..6d621c339 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -88,7 +88,7 @@ create or replace package body ut_suite_cache_manager is end; function group_paths_by_schema(a_paths ut_varchar2_list) return ut_path_items is - c_package_path_regex constant varchar2(100) := '^([A-Za-z0-9$#_]+)(\.([A-Za-z0-9$#_\*]+))?(\.([A-Za-z0-9$#_\*]+))?$'; + c_package_path_regex constant varchar2(100) := '^([[:alnum:]$#_]+)(\.([[:alnum:]$#_\*]+))?(\.([[:alnum:]$#_\*]+))?$'; l_results ut_path_items := ut_path_items(); l_path_item ut_path_item; i pls_integer; @@ -157,7 +157,7 @@ create or replace package body ut_suite_cache_manager is from schema_paths sp left outer join ut_suite_cache c on ( c.object_owner = upper(sp.schema_name) --and c.path like replace(sp.suite_path,'*','%')) - and regexp_like(c.path,'^'||replace(sp.suite_path,'*','[A-Za-z0-9$#_]*'))) + and regexp_like(c.path,'^'||replace(sp.suite_path,'*','[[:alnum:]$#_]*'))) where sp.suite_path is not null and instr(sp.suite_path,'*') > 0 ), straigth_suite_paths as ( @@ -220,55 +220,7 @@ create or replace package body ut_suite_cache_manager is and c.name = nvl(upper(sp.procedure_name),c.name)))) where r_num =1; return l_suite_items; end; - - /* - Having a base set of suites we will do a further filter down if there are - any tags defined. - */ - function get_tags_suites ( - a_suite_items ut_suite_cache_rows, - a_tags ut_varchar2_rows - ) return ut_suite_cache_rows is - l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows(); - l_include_tags ut_varchar2_rows; - l_exclude_tags ut_varchar2_rows; - begin - - select /*+ no_parallel */ column_value - bulk collect into l_include_tags - from table(a_tags) - where column_value not like '-%'; - - select /*+ no_parallel */ ltrim(column_value,'-') - bulk collect into l_exclude_tags - from table(a_tags) - where column_value like '-%'; - - with included_tags as ( - select c.path as path - from table(a_suite_items) c - where c.tags multiset intersect l_include_tags is not empty or l_include_tags is empty - ), - excluded_tags as ( - select c.path as path - from table(a_suite_items) c - where c.tags multiset intersect l_exclude_tags is not empty - ) - select value(c) as obj - bulk collect into l_suite_tags - from table(a_suite_items) c - where exists ( - select 1 from included_tags t - where t.path||'.' like c.path || '.%' /*all ancestors and self*/ - or c.path||'.' like t.path || '.%' /*all descendants and self*/ - ) - and not exists ( - select 1 from excluded_tags t - where c.path||'.' like t.path || '.%' /*all descendants and self*/ - ); - return l_suite_tags; - end; - + /* We will sort a suites in hierarchical structure. Sorting from bottom to top so when we consolidate @@ -321,31 +273,18 @@ create or replace package body ut_suite_cache_manager is end; function get_cached_suite_rows( - a_schema_paths ut_path_items, - a_random_seed positive := null, - a_tags ut_varchar2_rows := null + a_suites_filtered ut_suite_cache_rows ) return ut_suite_cache_rows is l_results ut_suite_cache_rows := ut_suite_cache_rows(); - l_suite_items ut_suite_cache_rows := ut_suite_cache_rows(); - l_schema_paths ut_path_items; - l_tags ut_varchar2_rows := coalesce(a_tags,ut_varchar2_rows()); begin - l_schema_paths := a_schema_paths; - l_suite_items := get_suite_items(a_schema_paths); - if l_tags.count > 0 then - l_suite_items := get_tags_suites(l_suite_items,l_tags); - end if; - - open c_get_bulk_cache_suite(l_suite_items); + open c_get_bulk_cache_suite(a_suites_filtered); fetch c_get_bulk_cache_suite bulk collect into l_results; close c_get_bulk_cache_suite; return l_results; end; - - function get_schema_parse_time(a_schema_name varchar2) return timestamp result_cache is l_cache_parse_time timestamp; begin @@ -492,7 +431,7 @@ create or replace package body ut_suite_cache_manager is a_schema_paths ut_path_items ) return ut_suite_cache_rows is begin - return get_cached_suite_rows( a_schema_paths ); + return get_cached_suite_rows(get_suite_items(a_schema_paths)); end; function get_suite_items_info( diff --git a/source/core/ut_suite_cache_manager.pks b/source/core/ut_suite_cache_manager.pks index 974babca5..81b1e0136 100644 --- a/source/core/ut_suite_cache_manager.pks +++ b/source/core/ut_suite_cache_manager.pks @@ -55,11 +55,9 @@ create or replace package ut_suite_cache_manager authid definer is * Not to be used publicly. Used internally for building suites at runtime. */ function get_cached_suite_rows( - a_schema_paths ut_path_items, - a_random_seed positive := null, - a_tags ut_varchar2_rows := null + a_suites_filtered ut_suite_cache_rows ) return ut_suite_cache_rows; - + function get_schema_paths(a_paths in ut_varchar2_list) return ut_path_items; /* @@ -74,6 +72,10 @@ create or replace package ut_suite_cache_manager authid definer is function get_suite_items_info( a_suite_cache_items ut_suite_cache_rows ) return ut_suite_items_info; + + function get_suite_items ( + a_schema_paths ut_path_items + ) return ut_suite_cache_rows; /* * Retrieves list of cached suite packages. @@ -94,6 +96,6 @@ create or replace package ut_suite_cache_manager authid definer is a_package_name varchar2, a_procedure_name varchar2 ) return boolean; - + end ut_suite_cache_manager; / diff --git a/source/core/ut_suite_manager.pkb b/source/core/ut_suite_manager.pkb index 805fd608b..c418de638 100644 --- a/source/core/ut_suite_manager.pkb +++ b/source/core/ut_suite_manager.pkb @@ -32,7 +32,7 @@ create or replace package body ut_suite_manager is for i in 1 .. a_paths.count loop l_path := a_paths(i); if l_path is null or not ( - regexp_like(l_path, '^[A-Za-z0-9$#_\*]+(\.[A-Za-z0-9$#_\*]+){0,2}$') or regexp_like(l_path, '^([A-Za-z0-9$#_]+)?:[A-Za-z0-9$#_\*]+(\.[A-Za-z0-9$#_\*]+)*$')) then + regexp_like(l_path, '^[[:alnum:]$#_\*]+(\.[[:alnum:]$#_\*]+){0,2}$') or regexp_like(l_path, '^([[:alnum:]$#_]+)?:[[:alnum:]$#_\*]+(\.[[:alnum:]$#_\*]+)*$')) then raise_application_error(ut_utils.gc_invalid_path_format, 'Invalid path format: ' || nvl(l_path, 'NULL')); end if; end loop; @@ -61,9 +61,9 @@ create or replace package body ut_suite_manager is for i in 1 .. a_paths.count loop --if path is suite-path - if regexp_like(a_paths(i), '^([A-Za-z0-9$#_]+)?:') then + if regexp_like(a_paths(i), '^([[:alnum:]$#_]+)?:') then --get schema name / path - l_schema := regexp_substr(a_paths(i), '^([A-Za-z0-9$#_]+)?:',subexpression => 1); + l_schema := regexp_substr(a_paths(i), '^([[:alnum:]$#_]+)?:',subexpression => 1); -- transform ":path1[.path2]" to "schema:path1[.path2]" if l_schema is not null then l_schema := sys.dbms_assert.schema_name(upper(l_schema)); @@ -78,7 +78,7 @@ create or replace package body ut_suite_manager is -- Object name or procedure is allowed to have filter char -- However this is not allowed on schema begin - l_object := regexp_substr(a_paths(i), '^[A-Za-z0-9$#_\*]+'); + l_object := regexp_substr(a_paths(i), '^[[:alnum:]$#_\*]+'); l_schema := sys.dbms_assert.schema_name(upper(l_object)); exception when sys.dbms_assert.invalid_schema_name then @@ -352,20 +352,18 @@ create or replace package body ut_suite_manager is function get_cached_suite_data( a_schema_paths ut_path_items, a_random_seed positive, - a_tags ut_varchar2_rows := null, + a_tags varchar2 := null, a_skip_all_objects boolean := false ) return t_cached_suites_cursor is - l_unfiltered_rows ut_suite_cache_rows; - l_filtered_rows ut_suite_cache_rows; - l_result t_cached_suites_cursor; + l_unfiltered_rows ut_suite_cache_rows; + l_tag_filter_applied ut_suite_cache_rows; + l_filtered_rows ut_suite_cache_rows; + l_result t_cached_suites_cursor; begin - l_unfiltered_rows := ut_suite_cache_manager.get_cached_suite_rows( - a_schema_paths, - a_random_seed, - a_tags - ); + l_unfiltered_rows := ut_suite_cache_manager.get_suite_items(a_schema_paths); - l_filtered_rows := get_filtered_cursor(l_unfiltered_rows,a_skip_all_objects); + l_tag_filter_applied := ut_suite_tag_filter.apply(l_unfiltered_rows,a_tags); + l_filtered_rows := get_filtered_cursor(ut_suite_cache_manager.get_cached_suite_rows(l_tag_filter_applied),a_skip_all_objects); reconcile_paths_and_suites(a_schema_paths,l_filtered_rows); ut_suite_cache_manager.sort_and_randomize_tests(l_filtered_rows,a_random_seed); @@ -451,7 +449,7 @@ create or replace package body ut_suite_manager is a_schema_paths ut_path_items, a_suites in out nocopy ut_suite_items, a_random_seed positive, - a_tags ut_varchar2_rows := null + a_tags varchar2 := null ) is begin reconstruct_from_cache( @@ -493,13 +491,21 @@ create or replace package body ut_suite_manager is return l_suites; end; - function get_schema_ut_packages(a_schema_names ut_varchar2_rows) return ut_object_names is + function get_schema_ut_packages(a_schema_names ut_varchar2_rows, a_schema_name_expr varchar2) return ut_object_names is + l_schema_names ut_varchar2_rows := a_schema_names; begin - for i in 1 .. a_schema_names.count loop - refresh_cache(a_schema_names(i)); + if a_schema_name_expr is not null then + select username + bulk collect into l_schema_names + from all_users + where regexp_like(username,a_schema_name_expr,'i'); + end if; + + for i in 1 .. l_schema_names.count loop + refresh_cache(l_schema_names(i)); end loop; - return ut_suite_cache_manager.get_cached_packages( a_schema_names ); + return ut_suite_cache_manager.get_cached_packages( l_schema_names ); end; function get_schema_names(a_paths ut_varchar2_list) return ut_varchar2_rows is @@ -520,7 +526,7 @@ create or replace package body ut_suite_manager is a_paths ut_varchar2_list, a_suites out nocopy ut_suite_items, a_random_seed positive := null, - a_tags ut_varchar2_rows := ut_varchar2_rows() + a_tags varchar2 := null ) is l_paths ut_varchar2_list := a_paths; l_schema_names ut_varchar2_rows; diff --git a/source/core/ut_suite_manager.pks b/source/core/ut_suite_manager.pks index 170b83f05..07539139a 100644 --- a/source/core/ut_suite_manager.pks +++ b/source/core/ut_suite_manager.pks @@ -30,7 +30,7 @@ create or replace package ut_suite_manager authid current_user is * @param a_schema_names list of schemas to return the information for * @return array containing unit test schema and object names */ - function get_schema_ut_packages(a_schema_names ut_varchar2_rows) return ut_object_names; + function get_schema_ut_packages(a_schema_names ut_varchar2_rows, a_schema_name_expr varchar2) return ut_object_names; /** * Builds a hierarchical suites based on given suite-paths @@ -53,7 +53,7 @@ create or replace package ut_suite_manager authid current_user is a_paths in ut_varchar2_list, a_suites out nocopy ut_suite_items, a_random_seed in positive := null, - a_tags ut_varchar2_rows := ut_varchar2_rows() + a_tags in varchar2 := null ); /** diff --git a/source/core/ut_suite_tag_filter.pkb b/source/core/ut_suite_tag_filter.pkb new file mode 100644 index 000000000..2d9436301 --- /dev/null +++ b/source/core/ut_suite_tag_filter.pkb @@ -0,0 +1,295 @@ +create or replace package body ut_suite_tag_filter is + /* + utPLSQL - Version 3 + Copyright 2016 - 2023 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + /** + * Constants use in postfix and infix transformations + */ + gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!'); + gc_unary_operators constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator + gc_binary_operators constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator + gc_reserved_tag_words constant ut_varchar2_list := ut_varchar2_list('none','any'); + gc_tags_column_name constant varchar2(250) := 'tags'; + gc_exception_msg constant varchar2(200) := 'Invalid tag expression'; + + type t_precedence_table is table of number index by varchar2(1); + g_precedence t_precedence_table; + + function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list is + l_tags_tokens ut_varchar2_list := ut_varchar2_list(); + begin + --Tokenize a string into operators and tags + select regexp_substr(a_tags,'([^!()|&]+)|([!()|&])', 1, level) as string_parts + bulk collect into l_tags_tokens + from dual connect by regexp_substr (a_tags, '([^!()|&]+)|([!()|&])', 1, level) is not null; + + return l_tags_tokens; + end; + + /* + To support a legact tag notation + , = OR + - = NOT + we will perform a replace of that characters into + new notation. + | = OR + & = AND + ! = NOT + */ + function replace_legacy_tag_notation(a_tags varchar2 + ) return varchar2 is + l_tags ut_varchar2_list := ut_utils.string_to_table(a_tags,','); + l_tags_include varchar2(4000); + l_tags_exclude varchar2(4000); + l_return_tag varchar2(4000); + begin + if instr(a_tags,',') > 0 or instr(a_tags,'-') > 0 then + + select '('||listagg( t.column_value,'|') + within group( order by column_value)||')' + into l_tags_include + from table(l_tags) t + where t.column_value not like '-%'; + + select '('||listagg( replace(t.column_value,'-','!'),' & ') + within group( order by column_value)||')' + into l_tags_exclude + from table(l_tags) t + where t.column_value like '-%'; + + + l_return_tag:= + case + when l_tags_include <> '()' and l_tags_exclude <> '()' + then l_tags_include || ' & ' || l_tags_exclude + when l_tags_include <> '()' + then l_tags_include + when l_tags_exclude <> '()' + then l_tags_exclude + end; + else + l_return_tag := a_tags; + end if; + return l_return_tag; + end; + + /* + https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression + */ + function shunt_logical_expression(a_tags in ut_varchar2_list) return ut_varchar2_list is + l_operator_stack ut_stack := ut_stack(); + l_rnp_tokens ut_varchar2_list := ut_varchar2_list(); + l_token varchar2(32767); + l_expect_operand boolean := true; + l_expect_operator boolean := false; + l_idx pls_integer; + begin + l_idx := a_tags.first; + --Exuecute modified shunting algorithm + WHILE (l_idx is not null) loop + l_token := a_tags(l_idx); + if (l_token member of gc_operators and l_token member of gc_binary_operators) then + if not(l_expect_operator) then + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); + end if; + while l_operator_stack.top > 0 and (g_precedence(l_operator_stack.peek) > g_precedence(l_token)) loop + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; + end loop; + l_operator_stack.push(a_tags(l_idx)); + l_expect_operand := true; + l_expect_operator:= false; + elsif (l_token member of gc_operators and l_token member of gc_unary_operators) then + if not(l_expect_operand) then + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); + end if; + l_operator_stack.push(a_tags(l_idx)); + l_expect_operand := true; + l_expect_operator:= false; + elsif l_token = '(' then + if not(l_expect_operand) then + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); + end if; + l_operator_stack.push(a_tags(l_idx)); + l_expect_operand := true; + l_expect_operator:= false; + elsif l_token = ')' then + if not(l_expect_operator) then + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); + end if; + while l_operator_stack.peek <> '(' loop + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; + end loop; + l_operator_stack.pop; --Pop the open bracket and discard it + l_expect_operand := false; + l_expect_operator:= true; + else + if not(l_expect_operand) then + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); + end if; + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) :=l_token; + l_expect_operator := true; + l_expect_operand := false; + end if; + + l_idx := a_tags.next(l_idx); + end loop; + + while l_operator_stack.peek is not null loop + if l_operator_stack.peek in ('(',')') then + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); + end if; + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last):=l_operator_stack.pop; + end loop; + + return l_rnp_tokens; + end shunt_logical_expression; + + function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list,a_tags_column_name in varchar2) + return varchar2 is + l_infix_stack ut_stack := ut_stack(); + l_right_side varchar2(32767); + l_left_side varchar2(32767); + l_infix_exp varchar2(32767); + l_member_token varchar2(20) := ' member of '||a_tags_column_name; + l_idx pls_integer; + begin + l_idx := a_postfix_exp.first; + while ( l_idx is not null) loop + --If the token we got is a none or any keyword + if a_postfix_exp(l_idx) member of gc_reserved_tag_words then + + l_infix_stack.push( + case + when a_postfix_exp(l_idx) = 'none' then '('||a_tags_column_name||' is empty or '||a_tags_column_name||' is null)' + else a_tags_column_name||' is not empty' + end + ); + --If token is operand but also single tag + elsif regexp_count(a_postfix_exp(l_idx),'[!()|&]') = 0 then + l_infix_stack.push(q'[']'||a_postfix_exp(l_idx)||q'[']'||l_member_token); + --If token is unary operator not + elsif a_postfix_exp(l_idx) member of gc_unary_operators then + l_right_side := l_infix_stack.pop; + l_infix_exp := a_postfix_exp(l_idx)||'('||l_right_side||')'; + l_infix_stack.push(l_infix_exp); + --If token is binary operator + elsif a_postfix_exp(l_idx) member of gc_binary_operators then + l_right_side := l_infix_stack.pop; + l_left_side := l_infix_stack.pop; + l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; + l_infix_stack.push(l_infix_exp); + end if; + l_idx := a_postfix_exp.next(l_idx); + end loop; + + return l_infix_stack.pop; + end conv_postfix_to_infix_sql; + + function create_where_filter(a_tags varchar2 + ) return varchar2 is + l_tags varchar2(4000); + begin + l_tags := replace(replace_legacy_tag_notation(a_tags),' '); + l_tags := conv_postfix_to_infix_sql(shunt_logical_expression(tokenize_tags_string(l_tags)),gc_tags_column_name); + l_tags := replace(l_tags, '|',' or '); + l_tags := replace(l_tags ,'&',' and '); + l_tags := replace(l_tags ,'!','not'); + return l_tags; + end; + + + /* + Having a base set of suites we will do a further filter down if there are + any tags defined. + */ + function get_tags_suites ( + a_suite_items ut_suite_cache_rows, + a_tags varchar2 + ) return ut_suite_cache_rows is + l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows(); + l_sql varchar2(32000); + l_tags varchar2(4000):= create_where_filter(a_tags); + begin + l_sql := + q'[ +with + suites_mv as ( + select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags as ]'||gc_tags_column_name||q'[ + from table(:suite_items) c + ), + suites_matching_expr as ( + select c.id,c.path as path,c.self_type,c.object_owner,c.tags + from suites_mv c + where c.self_type in ('UT_SUITE','UT_CONTEXT') + and ]'||l_tags||q'[ + ), + tests_matching_expr as ( + select c.id,c.path as path,c.self_type,c.object_owner,c.tags as ]'||gc_tags_column_name||q'[ + from suites_mv c where c.self_type in ('UT_TEST') + and ]'||l_tags||q'[ + ), + tests_with_tags_inh_from_suite as ( + select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags as ]'||gc_tags_column_name||q'[ ,c.object_owner + from suites_mv c join suites_matching_expr t + on (c.path||'.' like t.path || '.%' /*all descendants and self*/ and c.object_owner = t.object_owner) + ), + tests_with_tags_prom_to_suite as ( + select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags as ]'||gc_tags_column_name||q'[ ,c.object_owner + from suites_mv c join tests_matching_expr t + on (t.path||'.' like c.path || '.%' /*all ancestors and self*/ and c.object_owner = t.object_owner) + ) + select obj from suites_mv c, + (select id,row_number() over (partition by id order by id) r_num from + (select id + from tests_with_tags_prom_to_suite tst + where ]'||l_tags||q'[ + union all + select id from tests_with_tags_inh_from_suite tst + where ]'||l_tags||q'[ + ) + ) t where c.id = t.id and r_num = 1 ]'; + execute immediate l_sql bulk collect into l_suite_tags using a_suite_items; + return l_suite_tags; + end; + + function apply( + a_unfiltered_rows ut_suite_cache_rows, + a_tags varchar2 := null + ) return ut_suite_cache_rows is + l_suite_items ut_suite_cache_rows := a_unfiltered_rows; + begin + if length(a_tags) > 0 then + l_suite_items := get_tags_suites(l_suite_items,a_tags); + end if; + + return l_suite_items; + end; + +begin + --Define operators precedence + g_precedence('!'):=4; + g_precedence('&'):=3; + g_precedence('|'):=2; + g_precedence(')'):=1; + g_precedence('('):=1; + +end ut_suite_tag_filter; +/ diff --git a/source/core/ut_suite_tag_filter.pks b/source/core/ut_suite_tag_filter.pks new file mode 100644 index 000000000..e824ae275 --- /dev/null +++ b/source/core/ut_suite_tag_filter.pks @@ -0,0 +1,55 @@ +create or replace package ut_suite_tag_filter authid definer is + /* + utPLSQL - Version 3 + Copyright 2016 - 2023 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + /** + * Package that will filter suites by tags + * + */ + + /* + * Return table of tokens character by character + */ + function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list; + + /* + * Function that uses Dijkstra algorithm to parse mathematical and logical expression + * and return a list of elements in Reverse Polish Notation ( postfix ) + * As part of execution it will validate expression. + */ + function shunt_logical_expression(a_tags in ut_varchar2_list) return ut_varchar2_list; + + /* + * Function that converts postfix notation into infix and creating a string of sql filter + * that checking a tags collections for tags according to posted logic. + */ + function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list,a_tags_column_name in varchar2) + return varchar2; + + /* + * Generates a part where clause sql + */ + function create_where_filter(a_tags varchar2) + return varchar2; + + function apply( + a_unfiltered_rows ut_suite_cache_rows, + a_tags varchar2 := null + ) return ut_suite_cache_rows; + +end ut_suite_tag_filter; +/ diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 162e50f8f..81d47221b 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -19,11 +19,12 @@ create or replace package body ut_utils is /** * Constants regex used to validate XML name */ - gc_invalid_first_xml_char constant varchar2(50) := '[^_a-zA-Z]'; - gc_invalid_xml_char constant varchar2(50) := '[^_a-zA-Z0-9\.-]'; - gc_full_valid_xml_name constant varchar2(50) := '^([_a-zA-Z])([_a-zA-Z0-9\.-])*$'; + gc_invalid_first_xml_char constant varchar2(50) := '[^_[:alpha:]]'; + gc_invalid_xml_char constant varchar2(50) := '[^_[:alnum:]\.-]'; + gc_full_valid_xml_name constant varchar2(50) := '^([[:alpha:]])([_[:alnum:]\.-])*$'; gc_owner_hash constant integer(11) := dbms_utility.get_hash_value( ut_owner(), 0, power(2,31)-1); + function surround_with(a_value varchar2, a_quote_char varchar2) return varchar2 is begin return case when a_quote_char is not null then a_quote_char||a_value||a_quote_char else a_value end; @@ -91,8 +92,8 @@ create or replace package body ut_utils is a_max_output_len in number := gc_max_output_string_length ) return varchar2 is l_result varchar2(32767); - c_length constant integer := coalesce( length( a_value ), 0 ); - c_max_input_string_length constant integer := a_max_output_len - coalesce( length( a_quote_char ) * 2, 0 ); + c_length constant integer := coalesce( lengthb( a_value ), 0 ); + c_max_input_string_length constant integer := a_max_output_len - coalesce( lengthb( a_quote_char ) * 2, 0 ); c_overflow_substr_len constant integer := c_max_input_string_length - gc_more_data_string_len; begin if c_length = 0 then @@ -111,8 +112,8 @@ create or replace package body ut_utils is a_max_output_len in number := gc_max_output_string_length ) return varchar2 is l_result varchar2(32767); - c_length constant integer := coalesce(dbms_lob.getlength(a_value), 0); - c_max_input_string_length constant integer := a_max_output_len - coalesce( length( a_quote_char ) * 2, 0 ); + c_length constant integer := coalesce(ut_utils.lengthb_clob(a_value), 0); + c_max_input_string_length constant integer := a_max_output_len - coalesce( lengthb( a_quote_char ) * 2, 0 ); c_overflow_substr_len constant integer := c_max_input_string_length - gc_more_data_string_len; begin if a_value is null then @@ -134,7 +135,7 @@ create or replace package body ut_utils is ) return varchar2 is l_result varchar2(32767); c_length constant integer := coalesce(dbms_lob.getlength(a_value), 0); - c_max_input_string_length constant integer := a_max_output_len - coalesce( length( a_quote_char ) * 2, 0 ); + c_max_input_string_length constant integer := a_max_output_len - coalesce( lengthb( a_quote_char ) * 2, 0 ); c_overflow_substr_len constant integer := c_max_input_string_length - gc_more_data_string_len; begin if a_value is null then @@ -411,7 +412,7 @@ create or replace package body ut_utils is if a_list is null then a_list := ut_varchar2_rows(); end if; - if length(a_item) > gc_max_storage_varchar2_len then + if lengthb(a_item) > gc_max_storage_varchar2_len then append_to_list( a_list, ut_utils.convert_collection( @@ -467,7 +468,7 @@ create or replace package body ut_utils is l_result := ut_varchar2_rows(); for i in 1 .. a_collection.count loop l_result.extend(); - l_result(i) := substr(a_collection(i),1,gc_max_storage_varchar2_len); + l_result(i) := substrb(a_collection(i),1,gc_max_storage_varchar2_len); end loop; end if; return l_result; @@ -570,6 +571,7 @@ create or replace package body ut_utils is end loop; exit when l_lines_data%notfound; end loop; + close l_lines_data; execute immediate 'truncate table ut_dbms_output_cache'; commit; end; @@ -990,5 +992,37 @@ create or replace package body ut_utils is end; + /* + * Inspired by + * https://stackoverflow.com/a/48782891 + */ + function lengthb_clob( a_clob clob) return integer is + l_blob blob; + l_desc_offset PLS_INTEGER := 1; + l_src_offset PLS_INTEGER := 1; + l_lang PLS_INTEGER := 0; + l_warning PLS_INTEGER := 0; + l_result integer; + begin + if a_clob = empty_clob() then + l_result := 0; + elsif a_clob is not null then + dbms_lob.createtemporary(l_blob,true); + dbms_lob.converttoblob + ( l_blob + , a_clob + , dbms_lob.getlength(a_clob) + , l_desc_offset + , l_src_offset + , dbms_lob.default_csid + , l_lang + , l_warning + ); + l_result := length(l_blob); + dbms_lob.freetemporary(l_blob); + end if; + return l_result; + end; + end ut_utils; / diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index 6ef2c4a9a..0e1e0a851 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -21,7 +21,7 @@ create or replace package ut_utils authid definer is * */ - gc_version constant varchar2(50) := 'v3.1.13.4015-develop'; + gc_version constant varchar2(50) := 'v3.1.14.4206-develop'; subtype t_executable_type is varchar2(30); gc_before_all constant t_executable_type := 'beforeall'; @@ -121,6 +121,10 @@ create or replace package ut_utils authid definer is ex_failed_open_cur exception; gc_failed_open_cur constant pls_integer := -20218; pragma exception_init (ex_failed_open_cur, -20218); + + ex_invalid_tag_expression exception; + gc_invalid_tag_expression constant pls_integer := -20219; + pragma exception_init (ex_invalid_tag_expression, -20219); gc_max_storage_varchar2_len constant integer := 4000; gc_max_output_string_length constant integer := 4000; @@ -472,6 +476,11 @@ create or replace package ut_utils authid definer is * Return value of interval in plain english */ function interval_to_text(a_interval yminterval_unconstrained) return varchar2; + + /* + * Return length of CLOB in bytes. Null for NULL + */ + function lengthb_clob( a_clob clob) return integer; end ut_utils; / diff --git a/source/create_grants.sql b/source/create_grants.sql index 8a44ab371..0e9f46cc5 100644 --- a/source/create_grants.sql +++ b/source/create_grants.sql @@ -123,6 +123,7 @@ grant execute on &&ut3_owner..ut_console_reporter_base to &ut3_user; grant execute on &&ut3_owner..ut_output_buffer_base to &ut3_user; grant execute on &&ut3_owner..ut_output_table_buffer to &ut3_user; grant execute on &&ut3_owner..ut_output_clob_table_buffer to &ut3_user; +grant execute on &&ut3_owner..ut_output_bulk_buffer to &ut3_user; --needed internally for selecting from annotation objects within packages that use invoker rights grant execute on &&ut3_owner..ut_annotation_objs_cache_info to &ut3_user; diff --git a/source/create_utplsql_owner.sql b/source/create_utplsql_owner.sql index 1af2cf168..d7e4f3040 100644 --- a/source/create_utplsql_owner.sql +++ b/source/create_utplsql_owner.sql @@ -31,16 +31,13 @@ create user &ut3_owner_schema identified by "&ut3_password" default tablespace & grant create session, create sequence, create procedure, create type, create table, create view, create synonym to &ut3_owner_schema; -begin - $if dbms_db_version.version < 18 $then - execute immediate 'grant execute on dbms_lock to &ut3_owner_schema'; - $else - null; - $end -end; -/ - +grant execute on dbms_lock to &ut3_owner_schema; grant execute on dbms_crypto to &ut3_owner_schema; +grant execute on dbms_lob to &ut3_owner_schema; +grant execute on dbms_xmlgen to &ut3_owner_schema; +grant execute on dbms_sql to &ut3_owner_schema; +grant execute on dbms_random to &ut3_owner_schema; + grant alter session to &ut3_owner_schema; diff --git a/source/expectations/data_values/ut_compound_data_helper.pkb b/source/expectations/data_values/ut_compound_data_helper.pkb index 4517cd43c..875ec760a 100644 --- a/source/expectations/data_values/ut_compound_data_helper.pkb +++ b/source/expectations/data_values/ut_compound_data_helper.pkb @@ -300,8 +300,8 @@ create or replace package body ut_compound_data_helper is ut_utils.append_to_clob(a_partition_stmt,' row_number() over (partition by '||l_partition_tmp||' order by '||l_partition_tmp||' ) '); if a_pk_table.count > 0 then - -- If key defined do the join or these and where on diffrences - a_join_by_stmt := ut_utils.table_to_clob(l_join_by_list, ' and '); + -- If key defined do the join or these and where on diffrences as well as on duplicate number when rows are same. + a_join_by_stmt := ' e."UT3$_Dup#No" = a."UT3$_Dup#No" and '||ut_utils.table_to_clob(l_join_by_list, ' and '); elsif a_unordered then -- If no key defined do the join on all columns a_join_by_stmt := ' e."UT3$_Dup#No" = a."UT3$_Dup#No" and '||ut_utils.table_to_clob(l_equal_list, ' and '); diff --git a/source/expectations/data_values/ut_cursor_details.tpb b/source/expectations/data_values/ut_cursor_details.tpb index adc705f65..b823ff944 100644 --- a/source/expectations/data_values/ut_cursor_details.tpb +++ b/source/expectations/data_values/ut_cursor_details.tpb @@ -91,7 +91,11 @@ create or replace type body ut_cursor_details as a_cursor_number in number ) return self as result is l_columns_count pls_integer; + $if dbms_db_version.version = 12 and dbms_db_version.release = 1 or dbms_db_version.version < 12 $then l_columns_desc dbms_sql.desc_tab3; + $else + l_columns_desc dbms_sql.desc_tab4; + $end l_is_collection boolean; l_hierarchy_level integer := 1; begin diff --git a/source/expectations/matchers/ut_contain.tpb b/source/expectations/matchers/ut_contain.tpb index 7591925f5..c9691f731 100644 --- a/source/expectations/matchers/ut_contain.tpb +++ b/source/expectations/matchers/ut_contain.tpb @@ -47,6 +47,7 @@ create or replace type body ut_contain as overriding member function run_matcher_negated(self in out nocopy ut_contain, a_actual ut_data_value) return boolean is begin + self.negated(); return run_matcher(a_actual); end; diff --git a/source/expectations/ut_expectation.tpb b/source/expectations/ut_expectation.tpb index a78c57eb7..309759d48 100644 --- a/source/expectations/ut_expectation.tpb +++ b/source/expectations/ut_expectation.tpb @@ -686,7 +686,7 @@ create or replace type body ut_expectation as member procedure not_to_contain(self in ut_expectation, a_expected sys_refcursor) is begin - self.not_to( ut_contain(a_expected).negated() ); + self.not_to( ut_contain(a_expected)); end; member procedure to_contain(self in ut_expectation, a_expected anydata) is @@ -696,7 +696,7 @@ create or replace type body ut_expectation as member procedure not_to_contain(self in ut_expectation, a_expected anydata) is begin - self.not_to( ut_contain(a_expected).negated() ); + self.not_to( ut_contain(a_expected)); end; member function to_be_within(a_dist number) return ut_be_within is diff --git a/source/install.sql b/source/install.sql index 6f3c16801..827213e6c 100644 --- a/source/install.sql +++ b/source/install.sql @@ -50,6 +50,8 @@ create or replace context &&ut3_owner._info using &&ut3_owner..ut_session_contex @@install_component.sql 'core/types/ut_key_value_pairs.tps' @@install_component.sql 'core/types/ut_reporter_info.tps' @@install_component.sql 'core/types/ut_reporters_info.tps' +@@install_component.sql 'core/types/ut_stack.tps' +@@install_component.sql 'core/types/ut_stack.tpb' @@install_component.sql 'core/ut_utils.pks' @@install_component.sql 'core/ut_metadata.pks' @@install_component.sql 'core/ut_savepoint_seq.sql' @@ -120,6 +122,8 @@ create or replace context &&ut3_owner._info using &&ut3_owner..ut_session_contex @@install_component.sql 'core/output_buffers/ut_output_table_buffer.tpb' @@install_component.sql 'core/output_buffers/ut_output_clob_table_buffer.tps' @@install_component.sql 'core/output_buffers/ut_output_clob_table_buffer.tpb' +@@install_component.sql 'core/output_buffers/ut_output_bulk_buffer.tps' +@@install_component.sql 'core/output_buffers/ut_output_bulk_buffer.tpb' @@install_component.sql 'core/types/ut_output_reporter_base.tps' @@ -151,6 +155,8 @@ create or replace context &&ut3_owner._info using &&ut3_owner..ut_session_contex @@install_component.sql 'core/ut_suite_cache_seq.sql' @@install_component.sql 'core/ut_suite_cache.sql' +@@install_component.sql 'core/ut_suite_tag_filter.pks' +@@install_component.sql 'core/ut_suite_tag_filter.pkb' @@install_component.sql 'core/ut_suite_cache_manager.pks' @@install_component.sql 'core/ut_suite_cache_manager.pkb' @@install_component.sql 'core/ut_suite_builder.pks' diff --git a/source/reporters/ut_coverage_cobertura_reporter.tpb b/source/reporters/ut_coverage_cobertura_reporter.tpb index 48082a6d4..d833ac0aa 100644 --- a/source/reporters/ut_coverage_cobertura_reporter.tpb +++ b/source/reporters/ut_coverage_cobertura_reporter.tpb @@ -20,7 +20,7 @@ create or replace type body ut_coverage_cobertura_reporter is self in out nocopy ut_coverage_cobertura_reporter ) return self as result is begin - self.init($$plsql_unit); + self.init($$plsql_unit,ut_output_bulk_buffer()); return; end; @@ -29,11 +29,19 @@ create or replace type body ut_coverage_cobertura_reporter is l_report_lines ut_varchar2_list; l_coverage_data ut_coverage.t_coverage; - function get_lines_xml(a_unit_coverage ut_coverage.t_unit_coverage) return clob is + function get_line_rate(a_lines_covered in integer, a_lines_hit in integer) return varchar2 is + begin + return to_char(round((case a_lines_covered when 0 then 0 else a_lines_covered/a_lines_hit end), 17), rpad('FM0.0',20,'9') , 'NLS_NUMERIC_CHARACTERS=''. '''); + end; + + procedure get_lines_xml(a_unit_coverage ut_coverage.t_unit_coverage, + a_lines_result in out nocopy clob, a_lines_hits out number, a_lines_total out number) is l_file_part varchar2(32767); l_result clob; l_line_no binary_integer; l_pct integer; + l_lines_hits integer := 0; + l_lines_total integer := 0; begin dbms_lob.createtemporary(l_result, true); l_line_no := a_unit_coverage.lines.first; @@ -41,11 +49,14 @@ create or replace type body ut_coverage_cobertura_reporter is for i in 1 .. a_unit_coverage.total_lines loop ut_utils.append_to_clob(l_result, ''||chr(10)); end loop; + l_lines_hits:=0; + l_lines_total:= a_unit_coverage.total_lines; else while l_line_no is not null loop if a_unit_coverage.lines(l_line_no).executions = 0 then l_file_part := ''||chr(10); else + l_lines_hits:= l_lines_hits+1; l_file_part := '' + '' ); ut_utils.append_to_list( @@ -129,13 +150,13 @@ create or replace type body ut_coverage_cobertura_reporter is ut_utils.append_to_list( l_result, '' + ||dbms_xmlgen.convert(l_unit)||'" line-rate="'||get_line_rate(l_line_hits,l_line_total)||'" branch-rate="0.0" complexity="0.0">' ); ut_utils.append_to_list(l_result, ''); - - ut_utils.append_to_list( l_result, get_lines_xml(a_coverage_data.objects(l_unit)) ); - + ut_utils.append_to_list( l_result,l_lines_xml); + dbms_lob.freetemporary(l_lines_xml); + ut_utils.append_to_list(l_result, c_lines_footer); ut_utils.append_to_list(l_result, c_class_footer); ut_utils.append_to_list(l_result, c_classes_footer); diff --git a/source/reporters/ut_coverage_html_reporter.tpb b/source/reporters/ut_coverage_html_reporter.tpb index c88c91a80..b1f9f6651 100644 --- a/source/reporters/ut_coverage_html_reporter.tpb +++ b/source/reporters/ut_coverage_html_reporter.tpb @@ -22,7 +22,7 @@ create or replace type body ut_coverage_html_reporter is a_html_report_assets_path varchar2 := null ) return self as result is begin - self.init($$plsql_unit); + self.init($$plsql_unit,ut_output_bulk_buffer()); self.project_name := a_project_name; assets_path := nvl(a_html_report_assets_path, ut_coverage_report_html_helper.get_default_html_assets_path()); return; diff --git a/source/reporters/ut_coverage_report_html_helper.pkb b/source/reporters/ut_coverage_report_html_helper.pkb index 2bb9a87d9..f7e0b5ed0 100644 --- a/source/reporters/ut_coverage_report_html_helper.pkb +++ b/source/reporters/ut_coverage_report_html_helper.pkb @@ -134,9 +134,11 @@ create or replace package body ut_coverage_report_html_helper is l_file_part varchar2(32767); l_result ut_varchar2_rows := ut_varchar2_rows(); l_coverage_pct number(5, 2); - l_coverage_block_pct number(5, 2); l_hits varchar2(30); l_blocks varchar2(30); + l_line_text varchar2(32767); + e_buffer_too_small exception; + pragma exception_init ( e_buffer_too_small, -19011 ); begin l_coverage_pct := coverage_pct(a_coverage_unit.covered_lines, a_coverage_unit.uncovered_lines); @@ -148,10 +150,16 @@ create or replace package body ut_coverage_report_html_helper is ut_utils.append_to_list(l_result, l_file_part); for line_no in 1 .. a_source_code.count loop + begin + l_line_text := dbms_xmlgen.convert(a_source_code(line_no)); + exception + when e_buffer_too_small then + l_line_text := dbms_xmlgen.convert(to_clob(a_source_code(line_no))); + end; if not a_coverage_unit.lines.exists(line_no) then l_file_part := '
  • - ' || (dbms_xmlgen.convert(a_source_code(line_no))) || + ' || l_line_text || '
  • '; else l_hits := to_char(a_coverage_unit.lines(line_no).executions); @@ -188,7 +196,7 @@ create or replace package body ut_coverage_report_html_helper is ''; end if; l_file_part := l_file_part || ' - ' || (dbms_xmlgen.convert(a_source_code(line_no))) || + ' || l_line_text || ''; end if; ut_utils.append_to_list(l_result, l_file_part); @@ -222,7 +230,6 @@ create or replace package body ut_coverage_report_html_helper is l_file_part varchar2(32767); l_title varchar2(100) := 'All files'; l_coverage_pct number(5, 2); - l_coverage_block_pct number(5, 2); l_result ut_varchar2_rows; l_id varchar2(50) := object_id(a_title); l_unit_coverage ut_coverage.t_unit_coverage; diff --git a/source/reporters/ut_coverage_sonar_reporter.tpb b/source/reporters/ut_coverage_sonar_reporter.tpb index 8ee78dc24..1861a9be3 100644 --- a/source/reporters/ut_coverage_sonar_reporter.tpb +++ b/source/reporters/ut_coverage_sonar_reporter.tpb @@ -20,7 +20,7 @@ create or replace type body ut_coverage_sonar_reporter is self in out nocopy ut_coverage_sonar_reporter ) return self as result is begin - self.init($$plsql_unit); + self.init($$plsql_unit,ut_output_bulk_buffer()); return; end; @@ -84,6 +84,12 @@ create or replace type body ut_coverage_sonar_reporter is end; begin +-- execute immediate 'alter session set statistics_level=all'; +-- dbms_hprof.start_profiling( +-- location => 'PLSHPROF_DIR' +-- , filename => 'profiler_utPLSQL_run_on_'||$$plsql_unit||'_'||rawtohex(self.id)||'.txt' +-- ); +-- ut_coverage.coverage_stop(); self.print_text_lines( @@ -92,6 +98,7 @@ create or replace type body ut_coverage_sonar_reporter is a_run ) ); +-- dbms_hprof.stop_profiling; end; overriding member function get_description return varchar2 as diff --git a/source/reporters/ut_coveralls_reporter.tpb b/source/reporters/ut_coveralls_reporter.tpb index 3a54aa7f7..14672303e 100644 --- a/source/reporters/ut_coveralls_reporter.tpb +++ b/source/reporters/ut_coveralls_reporter.tpb @@ -20,7 +20,7 @@ create or replace type body ut_coveralls_reporter is self in out nocopy ut_coveralls_reporter ) return self as result is begin - self.init($$plsql_unit); + self.init($$plsql_unit,ut_output_bulk_buffer()); return; end; diff --git a/source/reporters/ut_junit_reporter.tpb b/source/reporters/ut_junit_reporter.tpb index 44affa030..3bceb52a4 100644 --- a/source/reporters/ut_junit_reporter.tpb +++ b/source/reporters/ut_junit_reporter.tpb @@ -18,7 +18,7 @@ create or replace type body ut_junit_reporter is constructor function ut_junit_reporter(self in out nocopy ut_junit_reporter) return self as result is begin - self.init($$plsql_unit); + self.init($$plsql_unit,ut_output_bulk_buffer()); return; end; diff --git a/source/reporters/ut_sonar_test_reporter.tpb b/source/reporters/ut_sonar_test_reporter.tpb index a3ec72241..7c46879d2 100644 --- a/source/reporters/ut_sonar_test_reporter.tpb +++ b/source/reporters/ut_sonar_test_reporter.tpb @@ -20,7 +20,7 @@ create or replace type body ut_sonar_test_reporter is self in out nocopy ut_sonar_test_reporter ) return self as result is begin - self.init($$plsql_unit); + self.init($$plsql_unit,ut_output_bulk_buffer()); return; end; diff --git a/source/reporters/ut_teamcity_reporter.tpb b/source/reporters/ut_teamcity_reporter.tpb index 95bea32b8..e05dc994f 100644 --- a/source/reporters/ut_teamcity_reporter.tpb +++ b/source/reporters/ut_teamcity_reporter.tpb @@ -108,23 +108,28 @@ create or replace type body ut_teamcity_reporter is ut_teamcity_reporter_helper.test_failed( a_test_name => l_test_full_name, a_msg => 'Error occured', - a_details => - trim(l_std_err_msg) - || case when a_test.failed_expectations is not null - and a_test.failed_expectations.count>0 - then a_test.failed_expectations(1).message end + a_details => trim(l_std_err_msg) ) ); + for i in 1 .. a_test.failed_expectations.count loop + ut_utils.append_to_list( + l_results, + ut_teamcity_reporter_helper.test_failed( + a_test_name => l_test_full_name, + a_msg => a_test.failed_expectations(i).description, + a_details => a_test.failed_expectations(i).message ) + ); + end loop; elsif a_test.failed_expectations is not null and a_test.failed_expectations.count > 0 then - -- Teamcity supports only a single failure message - - ut_utils.append_to_list( - l_results, - ut_teamcity_reporter_helper.test_failed( - a_test_name => l_test_full_name, - a_msg => a_test.failed_expectations(a_test.failed_expectations.first).description, - a_details => a_test.failed_expectations(a_test.failed_expectations.first).message ) - ); + for i in 1 .. a_test.failed_expectations.count loop + ut_utils.append_to_list( + l_results, + ut_teamcity_reporter_helper.test_failed( + a_test_name => l_test_full_name, + a_msg => a_test.failed_expectations(i).description, + a_details => a_test.failed_expectations(i).message ) + ); + end loop; elsif a_test.result = ut_utils.gc_failure then ut_utils.append_to_list( l_results, diff --git a/source/reporters/ut_teamcity_reporter_helper.pkb b/source/reporters/ut_teamcity_reporter_helper.pkb index 84a9d19e8..1633d10aa 100644 --- a/source/reporters/ut_teamcity_reporter_helper.pkb +++ b/source/reporters/ut_teamcity_reporter_helper.pkb @@ -73,8 +73,6 @@ create or replace package body ut_teamcity_reporter_helper is 'true' when false then 'false' - else - null end; l_props('flowId') := a_flow_id; return message('testStarted', l_props); diff --git a/source/reporters/ut_tfs_junit_reporter.tpb b/source/reporters/ut_tfs_junit_reporter.tpb index 710c3bc7f..5e36aad17 100644 --- a/source/reporters/ut_tfs_junit_reporter.tpb +++ b/source/reporters/ut_tfs_junit_reporter.tpb @@ -18,7 +18,7 @@ create or replace type body ut_tfs_junit_reporter is constructor function ut_tfs_junit_reporter(self in out nocopy ut_tfs_junit_reporter) return self as result is begin - self.init($$plsql_unit); + self.init($$plsql_unit,ut_output_bulk_buffer()); return; end; @@ -51,11 +51,12 @@ create or replace type body ut_tfs_junit_reporter is return regexp_substr(a_path_with_name, '(.*)\.' ||a_name||'$',subexpression=>1); end; - procedure print_test_results(a_test ut_test) is + function add_test_results(a_test ut_test) return ut_varchar2_rows is l_results ut_varchar2_rows := ut_varchar2_rows(); begin - self.print_text(''); + /* According to specs : - A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. @@ -83,64 +84,87 @@ create or replace type body ut_tfs_junit_reporter is ut_utils.append_to_list( l_results, ''); - self.print_text_lines(l_results); + return l_results; end; - procedure print_suite_results(a_suite ut_logical_suite, a_suite_id in out nocopy integer) is + procedure print_suite_results(a_suite ut_logical_suite, a_suite_id in out nocopy integer, a_nested_tests in out nocopy ut_varchar2_rows) is l_tests_count integer := a_suite.results_count.disabled_count + a_suite.results_count.success_count + a_suite.results_count.failure_count + a_suite.results_count.errored_count; l_results ut_varchar2_rows := ut_varchar2_rows(); l_suite ut_suite; l_outputs clob; l_errors ut_varchar2_list; - begin - + l_tests ut_varchar2_list; + begin for i in 1 .. a_suite.items.count loop - if a_suite.items(i) is of(ut_logical_suite) then - print_suite_results(treat(a_suite.items(i) as ut_logical_suite), a_suite_id); + if a_suite.items(i) is of(ut_suite_context) then + print_suite_results(treat(a_suite.items(i) as ut_suite_context), a_suite_id, a_nested_tests); + elsif a_suite.items(i) is of(ut_suite) then + print_suite_results(treat(a_suite.items(i) as ut_suite), a_suite_id, a_nested_tests); + elsif a_suite.items(i) is of(ut_logical_suite) then + print_suite_results(treat(a_suite.items(i) as ut_logical_suite), a_suite_id, a_nested_tests); end if; end loop; - - if a_suite is of(ut_suite) then - a_suite_id := a_suite_id + 1; - self.print_text(''); - self.print_text(''); + --Due to fact tha TFS and junit5 accepts only flat structure we have to report in suite level only. + if a_suite is of(ut_suite_context) then for i in 1 .. a_suite.items.count loop if a_suite.items(i) is of(ut_test) then - print_test_results(treat(a_suite.items(i) as ut_test)); + ut_utils.append_to_list( a_nested_tests,(add_test_results(treat(a_suite.items(i) as ut_test)))); end if; end loop; - l_suite := treat(a_suite as ut_suite); - l_outputs := l_suite.get_serveroutputs(); - if l_outputs is not null and l_outputs != empty_clob() then - ut_utils.append_to_list( l_results, ''); - ut_utils.append_to_list( l_results, ut_utils.to_cdata( l_suite.get_serveroutputs() ) ); - ut_utils.append_to_list( l_results, ''); - else - ut_utils.append_to_list( l_results, ''); - end if; - - l_errors := l_suite.get_error_stack_traces(); - if l_errors is not empty then - ut_utils.append_to_list( l_results, ''); - ut_utils.append_to_list( l_results, ut_utils.to_cdata( ut_utils.convert_collection( l_errors ) ) ); - ut_utils.append_to_list( l_results, ''); - else - ut_utils.append_to_list( l_results, ''); - end if; - ut_utils.append_to_list( l_results, ''); - - self.print_text_lines(l_results); + elsif a_suite is of(ut_suite) then + for i in 1 .. a_suite.items.count loop + if a_suite.items(i) is of(ut_test) then + ut_utils.append_to_list( a_nested_tests,(add_test_results(treat(a_suite.items(i) as ut_test)))); + end if; + end loop; + --TFS doesnt report on empty test suites, however all we want to make sure is that we dont pring parents suites + -- showing test count but not tests. + if (a_nested_tests.count > 0 and l_tests_count > 0) or (a_nested_tests.count = 0 and l_tests_count = 0) then + a_suite_id := a_suite_id + 1; + ut_utils.append_to_list( l_results,''); + ut_utils.append_to_list( l_results,''); + ut_utils.append_to_list(l_results,a_nested_tests); + l_suite := treat(a_suite as ut_suite); + l_outputs := l_suite.get_serveroutputs(); + if l_outputs is not null and l_outputs != empty_clob() then + ut_utils.append_to_list( l_results, ''); + ut_utils.append_to_list( l_results, ut_utils.to_cdata( l_suite.get_serveroutputs() ) ); + ut_utils.append_to_list( l_results, ''); + else + ut_utils.append_to_list( l_results, ''); + end if; + + l_errors := l_suite.get_error_stack_traces(); + if l_errors is not empty then + ut_utils.append_to_list( l_results, ''); + ut_utils.append_to_list( l_results, ut_utils.to_cdata( ut_utils.convert_collection( l_errors ) ) ); + ut_utils.append_to_list( l_results, ''); + else + ut_utils.append_to_list( l_results, ''); + end if; + ut_utils.append_to_list( l_results, ''); + + self.print_text_lines(l_results); + --We have resolved a context and we now reset value. + a_nested_tests := ut_varchar2_rows(); + end if; end if; end; + + procedure get_suite_results(a_suite ut_logical_suite, a_suite_id in out nocopy integer) is + l_nested_tests ut_varchar2_rows:= ut_varchar2_rows(); + begin + print_suite_results(a_suite, l_suite_id,l_nested_tests); + end; begin l_suite_id := 0; self.print_text(ut_utils.get_xml_header(a_run.client_character_set)); self.print_text(''); for i in 1 .. a_run.items.count loop - print_suite_results(treat(a_run.items(i) as ut_logical_suite), l_suite_id); + get_suite_results(treat(a_run.items(i) as ut_logical_suite), l_suite_id); end loop; self.print_text(''); end; diff --git a/source/reporters/ut_xunit_reporter.tpb b/source/reporters/ut_xunit_reporter.tpb index eab0c8a3c..4612fb640 100644 --- a/source/reporters/ut_xunit_reporter.tpb +++ b/source/reporters/ut_xunit_reporter.tpb @@ -18,7 +18,7 @@ create or replace type body ut_xunit_reporter is constructor function ut_xunit_reporter(self in out nocopy ut_xunit_reporter) return self as result is begin - self.init($$plsql_unit); + self.init($$plsql_unit,ut_output_bulk_buffer()); return; end; diff --git a/source/uninstall_objects.sql b/source/uninstall_objects.sql index ab4069497..dde9a824f 100644 --- a/source/uninstall_objects.sql +++ b/source/uninstall_objects.sql @@ -1,5 +1,4 @@ set echo off -set serverout on declare procedure drop_if_exists(a_object_type varchar2, a_object_name varchar2) is l_count integer; @@ -96,6 +95,8 @@ drop package ut_suite_manager; drop package ut_suite_builder; +drop package ut_suite_tag_filter; + drop package ut_suite_cache_manager; drop table ut_suite_cache purge; @@ -268,6 +269,8 @@ end; drop package ut_utils; +drop type ut_stack force; + drop sequence ut_savepoint_seq; drop type ut_documentation_reporter force; @@ -324,12 +327,18 @@ drop type ut_output_table_buffer force; drop type ut_output_clob_table_buffer force; +drop type ut_output_bulk_buffer force; + drop type ut_output_buffer_base force; drop table ut_output_buffer_tmp purge; +drop sequence ut_output_buffer_tmp_seq; + drop table ut_output_clob_buffer_tmp purge; +drop sequence ut_output_clob_buffer_tmp_seq; + drop table ut_output_buffer_info_tmp purge; drop package ut_session_context; diff --git a/source/uninstall_synonyms.sql b/source/uninstall_synonyms.sql index 569e5057d..2c96c03a4 100644 --- a/source/uninstall_synonyms.sql +++ b/source/uninstall_synonyms.sql @@ -15,10 +15,10 @@ begin and not exists (select 1 from all_objects o where o.owner = s.table_owner and o.object_name = s.table_name) ) loop - i := i + 1; begin execute immediate 'drop '||syn.syn_name; dbms_output.put_line('Dropped '||syn.syn_name||' for object '||syn.for_object); + i := i + 1; exception when others then dbms_output.put_line('FAILED to drop '||syn.syn_name||' for object '||syn.for_object); diff --git a/test/install_ut3_tester_tests.sql b/test/install_ut3_tester_tests.sql index 8163a39f2..cc96b2b64 100644 --- a/test/install_ut3_tester_tests.sql +++ b/test/install_ut3_tester_tests.sql @@ -17,6 +17,7 @@ alter session set plsql_optimize_level=0; @@ut3_tester/core/annotations/test_annot_disabled_reason.pks @@ut3_tester/core/expectations/test_expectation_processor.pks @@ut3_tester/core/test_ut_utils.pks +@@ut3_tester/core/test_ut_suite_tag_filter.pks @@ut3_tester/core/test_ut_test.pks @@ut3_tester/core/test_ut_suite.pks @@ut3_tester/core/test_ut_executable.pks @@ -35,6 +36,7 @@ alter session set plsql_optimize_level=0; @@ut3_tester/core/annotations/test_annot_disabled_reason.pkb @@ut3_tester/core/expectations/test_expectation_processor.pkb @@ut3_tester/core/test_ut_utils.pkb +@@ut3_tester/core/test_ut_suite_tag_filter.pkb @@ut3_tester/core/test_ut_test.pkb @@ut3_tester/core/test_ut_suite.pkb @@ut3_tester/core/test_ut_executable.pkb diff --git a/test/ut3_tester/core/test_output_buffer.pkb b/test/ut3_tester/core/test_output_buffer.pkb index 2e8b3337c..317e7e3d5 100644 --- a/test/ut3_tester/core/test_output_buffer.pkb +++ b/test/ut3_tester/core/test_output_buffer.pkb @@ -16,12 +16,13 @@ create or replace package body test_output_buffer is || chr(13) || chr(10) || to_clob(lpad('a text', 31000, ',a text')) || to_clob(lpad('a text', 31000, ',a text')); l_expected_item_type := lpad('some item type',1000,'-'); --Act + l_buffer.lock_buffer(); l_buffer.send_clob(l_expected_text, l_expected_item_type); l_buffer.close(); select text, item_type into l_actual_text, l_actual_item_type - from table(l_buffer.get_lines(0,0)); + from table(l_buffer.get_lines(0.1,0.1)); --Assert ut.expect(l_actual_text).to_equal(l_expected_text); @@ -32,7 +33,14 @@ create or replace package body test_output_buffer is ut.expect(l_remaining).to_equal(0); end; - + + procedure test_wait_for_producer is + l_buffer ut3_develop.ut_output_buffer_base; + begin + l_buffer := ut3_develop.ut_output_clob_table_buffer(); + ut.expect( l_buffer.get_lines_cursor(0.1) ).to_be_empty(); + end; + procedure test_doesnt_send_on_null_text is l_cur sys_refcursor; l_result integer; @@ -48,8 +56,8 @@ create or replace package body test_output_buffer is procedure test_doesnt_send_on_null_elem is - l_cur sys_refcursor; - l_result integer; + l_actual sys_refcursor; + l_expected sys_refcursor; l_buffer ut3_develop.ut_output_buffer_base := ut3_develop.ut_output_table_buffer(); l_message_id varchar2(255); l_text varchar2(4000); @@ -59,9 +67,12 @@ create or replace package body test_output_buffer is l_buffer.send_lines(ut3_develop.ut_varchar2_rows(null)); l_buffer.send_lines(ut3_develop.ut_varchar2_rows('test')); - select message_id, text into l_message_id, l_text from table(ut3_tester_helper.run_helper.ut_output_buffer_tmp); - ut.expect(l_message_id).to_equal('1'); - ut.expect(l_text).to_equal('test'); + open l_actual for + select text from table(ut3_tester_helper.run_helper.ut_output_buffer_tmp); + open l_expected for + select 'test' as text from dual; + + ut.expect(l_actual).to_equal(l_expected); end; procedure test_send_line is @@ -86,11 +97,12 @@ create or replace package body test_output_buffer is begin --Arrange l_expected := 'a text'; + l_buffer.lock_buffer(); l_buffer.send_line(l_expected); l_start := localtimestamp; --Act begin - select text into l_result from table(l_buffer.get_lines(1,1)); + select text into l_result from table(l_buffer.get_lines(0,0.3)); ut.fail('Expected a timeout exception but nothing was raised'); exception when others then @@ -101,7 +113,7 @@ create or replace package body test_output_buffer is --Throws a timeout exception ut.expect(dbms_utility.format_error_stack()).to_match('ORA'||ut3_develop.ut_utils.gc_out_buffer_timeout); --Waited for one second - ut.expect(l_duration).to_be_greater_than(interval '0.99' second); + ut.expect(l_duration).to_be_greater_or_equal(interval '0.3' second); end; select count(1) into l_remaining from table(ut3_tester_helper.run_helper.ut_output_buffer_tmp) where output_id = l_buffer.output_id; @@ -116,13 +128,15 @@ create or replace package body test_output_buffer is l_buffer ut3_develop.ut_output_buffer_base; begin --Arrange - l_stale_buffer.start_date := sysdate - 2; + l_stale_buffer.start_date := sysdate - 10; --initialize with new start date l_stale_buffer.init(); + l_stale_buffer.lock_buffer(); l_stale_buffer.send_line('some text'); l_stale_buffer.close(); l_fresh_buffer := ut3_develop.ut_output_table_buffer(); + l_fresh_buffer.lock_buffer(); l_fresh_buffer.send_line('some text'); l_fresh_buffer.close(); @@ -131,9 +145,9 @@ create or replace package body test_output_buffer is --Assert -- Data in "fresh" buffer remains - ut.expect( l_fresh_buffer.get_lines_cursor(0,0), l_buffer.self_type ).to_have_count(1); + ut.expect( l_fresh_buffer.get_lines_cursor(0,0), l_fresh_buffer.self_type ).to_have_count(1); -- Data in "stale" buffer is purged and so the call to get_lines_cursor throws ORA-20218 - ut.expect( l_stale_buffer.get_lines_cursor(0,0), l_buffer.self_type ).to_be_empty(); + ut.expect( l_stale_buffer.get_lines_cursor(0,0), l_stale_buffer.self_type ).to_be_empty(); end; procedure test_purge_text_buffer is @@ -146,5 +160,41 @@ create or replace package body test_output_buffer is test_purge(ut3_develop.ut_output_clob_table_buffer()); end; -end test_output_buffer; + procedure text_buffer_send_multibyte is + l_input varchar2(32767); + l_max_len integer := ut3_develop.ut_utils.gc_max_storage_varchar2_len; + l_buffer ut3_develop.ut_output_buffer_base := ut3_develop.ut_output_table_buffer(); + l_text varchar2(4000); + begin + --Arrange + ut3_tester_helper.run_helper.delete_buffer(); + l_input := rpad( '❤', l_max_len, 'a' ); + ut.expect( lengthb( l_input ) ).to_be_greater_than(l_max_len); + + --Act + l_buffer.send_line(l_input); + --Assert + select text into l_text from table(ut3_tester_helper.run_helper.ut_output_buffer_tmp); + ut.expect(lengthb(l_text)).to_be_less_or_equal(l_max_len); + end; + + procedure text_buffer_send_clob_multib is + l_input clob; + l_max_len integer := ut3_develop.ut_utils.gc_max_storage_varchar2_len; + l_buffer ut3_develop.ut_output_buffer_base := ut3_develop.ut_output_table_buffer(); + l_text varchar2(4000); + begin + --Arrange + ut3_tester_helper.run_helper.delete_buffer(); + l_input := rpad( '❤', l_max_len, 'a' ); + ut.expect( ut3_develop.ut_utils.lengthb_clob( l_input ) ).to_be_greater_than(l_max_len); + + --Act + l_buffer.send_clob(l_input); + --Assert + select text into l_text from table(ut3_tester_helper.run_helper.ut_output_buffer_tmp); + ut.expect(lengthb(l_text)).to_be_less_or_equal(l_max_len); + end; + + end test_output_buffer; / diff --git a/test/ut3_tester/core/test_output_buffer.pks b/test/ut3_tester/core/test_output_buffer.pks index 24c2c01eb..5b6cd678b 100644 --- a/test/ut3_tester/core/test_output_buffer.pks +++ b/test/ut3_tester/core/test_output_buffer.pks @@ -2,10 +2,33 @@ create or replace package test_output_buffer is --%suite(output_buffer) --%suitepath(utplsql.ut3_tester.core) + + + --%context(Read and write within the same session) + + + --%endcontext + --%context(Buffer is read in a different session than buffer write) + + --reader will wait for a_initial_timeout seconds for the writer process to start and then it will finish with error + + --reader will wait forever (beyond a_initial_timeout) if the writer process is started and end of data row was not received from the buffer + + --reader stops after reading the end of data signal from the buffer + + --reader stops when writer process ends and all data was read from the buffer + + + --%endcontext + --%test(Receives a line from buffer table and deletes) procedure test_receive; + --%test(Waits specified time for producer to lock the buffer ) + --%throws(-20218) + procedure test_wait_for_producer; + --%test(Does not send line if null text given) procedure test_doesnt_send_on_null_text; @@ -19,12 +42,16 @@ create or replace package test_output_buffer is procedure test_waiting_for_data; --%test(Purges text buffer data older than one day and leaves the rest) - --%throws(-20218) procedure test_purge_text_buffer; --%test(Purges clob buffer data older than one day and leaves the rest) - --%throws(-20218) procedure test_purge_clob_buffer; + --%test(Successfully sends multibyte long line into text buffer) + procedure text_buffer_send_multibyte; + + --%test(Successfully sends multibyte long clob line into text buffer) + procedure text_buffer_send_clob_multib; + end test_output_buffer; / diff --git a/test/ut3_tester/core/test_suite_manager.pkb b/test/ut3_tester/core/test_suite_manager.pkb index fbac0f3f5..c818ad212 100644 --- a/test/ut3_tester/core/test_suite_manager.pkb +++ b/test/ut3_tester/core/test_suite_manager.pkb @@ -2217,5 +2217,22 @@ end;]'; end loop; end; + --%test(Path validation does not fail on Estonian NLS_SORT - fix #1252) + procedure path_validate_nls_sort is + l_schema_names ut3_develop.ut_varchar2_rows; + begin + --Arrange + execute immediate q'[alter session set nls_sort='estonian']'; + --Act + l_schema_names := ut3_develop.ut_suite_manager.get_schema_names(ut3_develop.ut_varchar2_list('ut3')); + --Asseert + ut.expect(sqlcode).to_equal(0); + end; + + + procedure reset_nls_sort is + begin + execute immediate q'[alter session set nls_sort='binary']'; + end; end test_suite_manager; / diff --git a/test/ut3_tester/core/test_suite_manager.pks b/test/ut3_tester/core/test_suite_manager.pks index d9d4efc09..ac7818f4f 100644 --- a/test/ut3_tester/core/test_suite_manager.pks +++ b/test/ut3_tester/core/test_suite_manager.pks @@ -237,5 +237,15 @@ create or replace package test_suite_manager is --%endcontext + --%context(paths validation) + + --%test(Path validation does not fail on Estonian NLS_SORT - fix #1252) + --%aftertest(reset_nls_sort) + procedure path_validate_nls_sort; + + procedure reset_nls_sort; + + --%endcontext + end test_suite_manager; / diff --git a/test/ut3_tester/core/test_ut_suite_tag_filter.pkb b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb new file mode 100644 index 000000000..edfb27cfc --- /dev/null +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb @@ -0,0 +1,82 @@ +create or replace package body test_ut_suite_tag_filter is + + procedure test_conversion_to_rpn is + l_postfix ut3_develop.ut_varchar2_list; + l_postfix_string varchar2(4000); + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_suite_tag_filter.tokenize_tags_string('A'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('A'); + + l_input_token := ut3_develop.ut_suite_tag_filter.tokenize_tags_string('A|B'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('AB|'); + + l_input_token := ut3_develop.ut_suite_tag_filter.tokenize_tags_string('(a|b)|c&d'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('ab|cd&|'); + + l_input_token := ut3_develop.ut_suite_tag_filter.tokenize_tags_string('!a|b'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('a!b|'); + end; + + procedure test_conversion_opr_by_opr is + l_postfix ut3_develop.ut_varchar2_list; + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_varchar2_list('A','B'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + ut.fail('Expected exception but nothing was raised'); + end; + + procedure test_conversion_oprd_by_opd is + l_postfix ut3_develop.ut_varchar2_list; + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_varchar2_list('|','|'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + ut.fail('Expected exception but nothing was raised'); + end; + + procedure test_conversion_lb_by_oper is + l_postfix ut3_develop.ut_varchar2_list; + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_varchar2_list('(','A','|','B',')','('); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + ut.fail('Expected exception but nothing was raised'); + end; + + procedure test_conversion_rb_by_oprd is + l_postfix ut3_develop.ut_varchar2_list; + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_varchar2_list(')','A'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + ut.fail('Expected exception but nothing was raised'); + end; + + procedure conv_from_tag_to_sql_filter is + l_sql_filter varchar2(4000); + begin + l_sql_filter := ut3_develop.ut_suite_tag_filter.create_where_filter('test1'); + ut.expect(l_sql_filter).to_equal(q'['test1' member of tags]'); + + l_sql_filter := ut3_develop.ut_suite_tag_filter.create_where_filter('test1|test2'); + ut.expect(l_sql_filter).to_equal(q'[('test1' member of tags or 'test2' member of tags)]'); + + l_sql_filter := ut3_develop.ut_suite_tag_filter.create_where_filter('test1|!test2'); + ut.expect(l_sql_filter).to_equal(q'[('test1' member of tags or not('test2' member of tags))]'); + + l_sql_filter := ut3_develop.ut_suite_tag_filter.create_where_filter('test1&!test2'); + ut.expect(l_sql_filter).to_equal(q'[('test1' member of tags and not('test2' member of tags))]'); + end; + +end test_ut_suite_tag_filter; +/ diff --git a/test/ut3_tester/core/test_ut_suite_tag_filter.pks b/test/ut3_tester/core/test_ut_suite_tag_filter.pks new file mode 100644 index 000000000..0f84b751b --- /dev/null +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pks @@ -0,0 +1,33 @@ +create or replace package test_ut_suite_tag_filter is + + --%suite(ut_suite_tag_filter) + --%suitepath(utplsql.ut3_tester.core) + + --%context( Conversion to Reverse Polish Notation) + + --%test( Test conversion of expression into Reverse Polish Notation) + procedure test_conversion_to_rpn; + + --%test( Operator is followed by operator) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure test_conversion_opr_by_opr; + + --%test( Operand is followed by operand) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure test_conversion_oprd_by_opd; + + --%test( Left Bracket is followed by operator) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure test_conversion_lb_by_oper; + + --%test( Right Bracket is followed by operand) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure test_conversion_rb_by_oprd; + + --%endcontext + + --%test( Test conversion of expression from tag into custom where filter for SQL) + procedure conv_from_tag_to_sql_filter; + +end test_ut_suite_tag_filter; +/ diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 4ed718777..433987c01 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -488,6 +488,24 @@ end; begin ut.expect(l_expected).to_equal(l_actual); end; - + + procedure convert_collection_multibyte is + l_input ut3_develop.ut_varchar2_list; + l_max_len integer := ut3_develop.ut_utils.gc_max_storage_varchar2_len; + begin + --Arrange + l_input := ut3_develop.ut_varchar2_list( rpad( '❤', l_max_len, 'a' ) ); + ut.expect( lengthb( l_input( 1 ) ) ).to_be_greater_than(l_max_len); + + --Act + ut.expect( lengthb( ut3_develop.ut_utils.convert_collection(l_input)(1) ) ).to_be_less_or_equal(l_max_len); + end; + + procedure lengthb_gives_length_in_bytes is + l_clob clob; + begin + l_clob := '❤'; + ut.expect(ut3_develop.ut_utils.lengthb_clob(l_clob)).to_be_greater_than(1); + end; end test_ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index 4d83b5042..ca2e7b304 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -152,10 +152,15 @@ create or replace package test_ut_utils is procedure int_conv_ym_month; --%test(returns text representation of interval year to month for custom interval) - procedure int_conv_ym_date; - - + procedure int_conv_ym_date; + --%endcontext + --%test(convert_collection does not fail on multibyte strings - Issue #1245 ) + procedure convert_collection_multibyte; + + --%test(lengthb returns length of a CLOB in bytes ) + procedure lengthb_gives_length_in_bytes; + end test_ut_utils; / diff --git a/test/ut3_tester_helper/coverage_helper.pkb b/test/ut3_tester_helper/coverage_helper.pkb index fdfa32364..ef14c586f 100644 --- a/test/ut3_tester_helper/coverage_helper.pkb +++ b/test/ut3_tester_helper/coverage_helper.pkb @@ -48,6 +48,20 @@ create or replace package body coverage_helper is end; end;]'; + execute immediate q'[create or replace package ut3_develop.some_other_package is + procedure do_stuff(i_input in number); + end;]'; + + execute immediate q'[create or replace package body ut3_develop.some_other_package is + procedure do_stuff(i_input in number) is + begin + if i_input = 2 then dbms_output.put_line('should not get here'); elsif i_input = 1 then dbms_output.put_line('should get here'); + else + dbms_output.put_line('should not get here'); + end if; + end; + end;]'; + execute immediate q'[create or replace package ut3_develop.test_dummy_coverage is --%suite(dummy coverage test) --%suitepath(coverage_testing) @@ -77,6 +91,7 @@ create or replace package body coverage_helper is pragma autonomous_transaction; begin begin execute immediate q'[drop package ut3_develop.test_dummy_coverage]'; exception when others then null; end; + begin execute immediate q'[drop package ut3_develop.some_other_package]'; exception when others then null; end; begin execute immediate q'[drop package ut3_develop.]'||covered_package_name; exception when others then null; end; end; @@ -275,11 +290,11 @@ create or replace package body coverage_helper is ut3_develop.ut_runner.coverage_stop(); end; - function get_job_status(a_job_name varchar2, a_job_started_after timestamp with time zone) return varchar2 is - l_status varchar2(1000); + function get_job_status(a_job_name varchar2, a_job_started_after timestamp with time zone) return user_scheduler_job_run_details%rowtype is + l_result user_scheduler_job_run_details%rowtype; begin begin - select status into l_status + select * into l_result from user_scheduler_job_run_details where job_name = upper(a_job_name) and req_start_date >= a_job_started_after; @@ -287,20 +302,11 @@ create or replace package body coverage_helper is when no_data_found then null; end; - return l_status; - end; - - procedure sleep(a_time number) is - begin - $if dbms_db_version.version >= 18 $then - dbms_session.sleep(a_time); - $else - dbms_lock.sleep(a_time ); - $end + return l_result; end; procedure run_job_and_wait_for_finish(a_job_action varchar2) is - l_status varchar2(1000); + l_job_run_info user_scheduler_job_run_details%rowtype; l_job_name varchar2(30); l_timestamp timestamp with time zone := current_timestamp; i integer := 0; @@ -308,7 +314,7 @@ create or replace package body coverage_helper is begin g_job_no := g_job_no + 1; l_job_name := 'utPLSQL_selftest_job_'||g_job_no; - sleep(0.15); + dbms_lock.sleep(0.15); dbms_scheduler.create_job( job_name => l_job_name, job_type => 'PLSQL_BLOCK', @@ -318,14 +324,14 @@ create or replace package body coverage_helper is auto_drop => TRUE, comments => 'one-time-job' ); - while (l_status is null or l_status not in ('SUCCEEDED','FAILED')) and i < 150 loop - l_status := get_job_status( l_job_name, l_timestamp ); - sleep(0.1); + while (l_job_run_info.status is null or l_job_run_info.status not in ('SUCCEEDED','FAILED')) and i < 6000 loop + l_job_run_info := get_job_status( l_job_name, l_timestamp ); + dbms_lock.sleep(0.1); i := i + 1; end loop; commit; - if l_status = 'FAILED' then - raise_application_error(-20000, 'Running a scheduler job failed'); + if nvl(l_job_run_info.status,'null') <> 'SUCCEEDED' then + raise_application_error(-20000, 'Scheduler job '''||l_job_name||''', status='''||l_job_run_info.status||'''. Additional info: '||l_job_run_info.additional_info); end if; end; @@ -363,7 +369,7 @@ create or replace package body coverage_helper is pragma autonomous_transaction; begin run_job_and_wait_for_finish( a_plsql_block ); - + dbms_lock.sleep(0.1); execute immediate q'[ declare l_results ut3_develop.ut_varchar2_list; @@ -483,16 +489,30 @@ create or replace package body coverage_helper is l_coverage_id raw(32) := sys_guid(); begin l_plsql_block := q'[ + declare + x dbms_output.chararr; + i integer := 100000; begin +/* + execute immediate 'alter session set statistics_level=all'; + dbms_hprof.start_profiling( + location => 'PLSHPROF_DIR' + , filename => 'profiler_utPLSQL_run_]'||rawtohex(l_coverage_id)||q'[.txt' + ); +*/ ut3_develop.ut_runner.coverage_start(']'||rawtohex(l_coverage_id)||q'['); ut3_develop.ut_coverage.set_develop_mode(a_develop_mode => true); --gather coverage on the command executed begin {a_run_command}; end; + dbms_output.get_lines(x,i); ut3_develop.ut_coverage.set_develop_mode(a_develop_mode => false); ut3_develop.ut_runner.coverage_stop(); --get the actual results of the command gathering the coverage insert into test_results select rownum as id, x.* from table( {a_run_command} ) x; commit; +/* + dbms_hprof.stop_profiling; +*/ end;]'; l_plsql_block := replace(l_plsql_block,'{a_run_command}',a_run_command); l_result_clob := run_code_as_job( l_plsql_block ); diff --git a/test/ut3_tester_helper/run_helper.pkb b/test/ut3_tester_helper/run_helper.pkb index 95f93f751..6289b1642 100644 --- a/test/ut3_tester_helper/run_helper.pkb +++ b/test/ut3_tester_helper/run_helper.pkb @@ -343,9 +343,250 @@ create or replace package body run_helper is end; end test_package_3; ]'; + + execute immediate q'[create or replace package test_tag_pkg_1 is + --%suite + --%tags(suite1,release_3_1_13,development,complex,end_to_end) + --%suitepath(suite1) + --%rollback(manual) + + --%test(Test1 from test_tag_pkg_1) + --%tags(test1,development,fast) + procedure test1; + + --%test(Test2 from test_tag_pkg_1) + --%tags(test2,production,slow,patch_3_1_13) + procedure test2; + + end test_tag_pkg_1; + ]'; + + execute immediate q'[create or replace package body test_tag_pkg_1 is + procedure test1 is + begin + dbms_output.put_line('test_tag_pkg_1.test1 executed'); + end; + procedure test2 is + begin + dbms_output.put_line('test_tag_pkg_1.test2 executed'); + end; + end test_tag_pkg_1; + ]'; + + execute immediate q'[create or replace package test_tag_pkg_2 is + --%suite + --%tags(suite2,release_3_1_12,development,simple) + --%suitepath(suite1.suite2) + --%rollback(manual) + + --%test(Test3 from test_tag_pkg_2) + --%tags(test3,development,fast) + procedure test3; + + --%test(Test4 from test_tag_pkg_1) + --%tags(test4,production,slow) + procedure test4; + + end test_tag_pkg_2; + ]'; + + execute immediate q'[create or replace package body test_tag_pkg_2 is + procedure test3 is + begin + dbms_output.put_line('test_tag_pkg_2.test3 executed'); + end; + procedure test4 is + begin + dbms_output.put_line('test_tag_pkg_2.test4 executed'); + end; + end test_tag_pkg_2; + ]'; + + execute immediate q'[create or replace package test_tag_pkg_3 is + --%suite + --%tags(suite3,release_3_1_13,production,simple,end_to_end) + --%suitepath(suite3) + --%rollback(manual) + + --%test(Test5 from test_tag_pkg_3) + --%tags(test5,release_3_1_13,production,patch_3_1_13) + procedure test5; + + --%test(Test6 from test_tag_pkg_3) + --%tags(test6,development,patch_3_1_14) + procedure test6; + + end test_tag_pkg_3; + ]'; + + execute immediate q'[create or replace package body test_tag_pkg_3 is + procedure test5 is + begin + dbms_output.put_line('test_tag_pkg_3.test5 executed'); + end; + procedure test6 is + begin + dbms_output.put_line('test_tag_pkg_3.test6 executed'); + end; + end test_tag_pkg_3; + ]'; + + execute immediate q'[create or replace package suite1_level1_pkg is + + --%suite(suite1_level1) + --%suitepath(any_none) + --%rollback(manual) + + --%test(Test 1 from Suite1 on level 1) + --%tags(suite1,level1,test1,test1_level1) + procedure test1_level1; + + --%test(Test 2 from Suite1 on level 1) + procedure test2_level1; + + end suite1_level1_pkg; + ]'; + + execute immediate q'[create or replace package body suite1_level1_pkg is + procedure test1_level1 is + begin + dbms_output.put_line('suite1_level1_pkg.test1_level1 executed'); + end; + procedure test2_level1 is + begin + dbms_output.put_line('suite1_level1_pkg.test2_level1 executed'); + end; + end suite1_level1_pkg; + ]'; + + execute immediate q'[create or replace package suite1_1_level2_pkg is + + --%suite(suite1_1_level2) + --%suitepath(any_none.suite1_level1) + --%rollback(manual) + + --%test(Test 1 from Suite1_2 on level 2) + --%tags(level2,test1,test1_level2) + procedure suite1_1_test1_level2; + + --%test(Test 2 from Suite1_2 on level 2) + procedure suite1_1_test2_level2; + + end suite1_1_level2_pkg; + ]'; + + execute immediate q'[create or replace package body suite1_1_level2_pkg is + procedure suite1_1_test1_level2 is + begin + dbms_output.put_line('suite1_1_level2_pkg.suite1_1_test1_level2 executed'); + end; + procedure suite1_1_test2_level2 is + begin + dbms_output.put_line('suite1_1_level2_pkg.suite1_1_test2_level2 executed'); + end; + end suite1_1_level2_pkg; + ]'; + + execute immediate q'[create or replace package suite1_2_level2_pkg is + + --%suite(suite1_2_level2) + --%tags(level2,suite1_2,suites) + --%suitepath(any_none.suite1_level1) + --%rollback(manual) + + --%test(Test 1 from Suite1_2 on level 2) + procedure suite1_2_test1_level2; + + --%test(Test 2 from Suite1_2 on level 2) + --%tags(level2,test2,test2_level2) + procedure suite1_2_test2_level1; + + end suite1_2_level2_pkg; + ]'; + + execute immediate q'[create or replace package body suite1_2_level2_pkg is + procedure suite1_2_test1_level2 is + begin + dbms_output.put_line('suite1_2_level2_pkg.suite1_2_test1_level2 executed'); + end; + procedure suite1_2_test2_level1 is + begin + dbms_output.put_line('suite1_2_level2_pkg.suite1_2_test2_level1 executed'); + end; + end suite1_2_level2_pkg; + ]'; + + execute immediate q'[create or replace package suite2_level1_pkg is + + --%suite(suite2_level1) + --%tags(level1,suite2,suites) + --%suitepath(any_none) + --%rollback(manual) + + --%test(Test 1 from Suite1 on level 1) + --%tags(suite2,level1,test1,test1_level1) + procedure test1_level1; + + --%test(Test 2 from Suite1 on level 1) + procedure test2_level1; + + end suite2_level1_pkg; + ]'; + + execute immediate q'[create or replace package body suite2_level1_pkg is + procedure test1_level1 is + begin + dbms_output.put_line('suite2_level1_pkg.test1_level1 executed'); + end; + procedure test2_level1 is + begin + dbms_output.put_line('suite2_level1_pkg.test2_level1 executed'); + end; + end suite2_level1_pkg; + ]'; + + execute immediate q'[create or replace package suite2_2_level2_pkg is + + --%suite(suite2_2_level2) + --%tags(level2,suite2_2,suites) + --%suitepath(any_none.suite2_level1) + --%rollback(manual) + + --%test(Test 1 from Suite2_2 on level 2) + procedure suite2_2_test1_level2; + + --%test(Test 2 from Suite2_2 on level 2) + --%tags(level2,test2,test2_level2) + procedure suite2_2_test2_level2; + + end suite2_2_level2_pkg; + ]'; + + execute immediate q'[create or replace package body suite2_2_level2_pkg is + procedure suite2_2_test1_level2 is + begin + dbms_output.put_line('suite2_2_level2_pkg.suite2_2_test1_level2 executed'); + end; + procedure suite2_2_test2_level2 is + begin + dbms_output.put_line('suite2_2_level2_pkg.suite2_2_test2_level2 executed'); + end; + end suite2_2_level2_pkg; + ]'; + + execute immediate q'[grant execute on test_package_1 to public]'; execute immediate q'[grant execute on test_package_2 to public]'; execute immediate q'[grant execute on test_package_3 to public]'; + execute immediate q'[grant execute on test_tag_pkg_1 to public]'; + execute immediate q'[grant execute on test_tag_pkg_2 to public]'; + execute immediate q'[grant execute on test_tag_pkg_3 to public]'; + + execute immediate q'[grant execute on suite1_level1_pkg to public]'; + execute immediate q'[grant execute on suite1_1_level2_pkg to public]'; + execute immediate q'[grant execute on suite1_2_level2_pkg to public]'; + execute immediate q'[grant execute on suite2_level1_pkg to public]'; + execute immediate q'[grant execute on suite2_2_level2_pkg to public]'; end; procedure drop_ut3_user_tests is @@ -354,6 +595,15 @@ create or replace package body run_helper is execute immediate q'[drop package test_package_1]'; execute immediate q'[drop package test_package_2]'; execute immediate q'[drop package test_package_3]'; + execute immediate q'[drop package test_tag_pkg_1]'; + execute immediate q'[drop package test_tag_pkg_2]'; + execute immediate q'[drop package test_tag_pkg_3]'; + + execute immediate q'[drop package suite2_2_level2_pkg]'; + execute immediate q'[drop package suite2_level1_pkg]'; + execute immediate q'[drop package suite1_2_level2_pkg]'; + execute immediate q'[drop package suite1_level1_pkg]'; + execute immediate q'[drop package suite1_1_level2_pkg]'; end; procedure create_test_suite is @@ -616,7 +866,7 @@ create or replace package body run_helper is function get_schema_ut_packages(a_owner in varchar2) return ut3_develop.ut_object_names is begin - return ut3_develop.ut_suite_manager.get_schema_ut_packages(ut3_develop.ut_varchar2_rows(a_owner)); + return ut3_develop.ut_suite_manager.get_schema_ut_packages(ut3_develop.ut_varchar2_rows(a_owner), null); end; function ut_output_buffer_tmp return t_out_buff_tab pipelined is diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 41b969359..f0cfd8373 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -742,7 +742,7 @@ Failures:% procedure remove_time_from_results(a_results in out nocopy ut3_develop.ut_varchar2_list) is begin for i in 1 .. a_results.count loop - a_results(i) := regexp_replace(a_results(i),'\[[0-9]*[\.,][0-9]+ sec\]',''); + a_results(i) := regexp_replace(a_results(i),'\[[0-9]*[\.,]?[0-9]+ sec\]',''); a_results(i) := regexp_replace(a_results(i),'Finished in [0-9]*[\.,][0-9]+ seconds',''); end loop; end; @@ -948,7 +948,7 @@ Failures:% procedure two_test_run_by_two_tags is l_results clob; begin - ut3_tester_helper.run_helper.run(a_tags => 'subtest1,subtest2'); + ut3_tester_helper.run_helper.run(a_tags => 'subtest1|subtest2'); l_results := ut3_tester_helper.main_helper.get_dbms_output_as_clob(); --Assert ut.expect( l_results ).to_be_like( '%test_package_1%' ); @@ -958,7 +958,21 @@ Failures:% ut.expect( l_results ).not_to_be_like( '%test_package_3%' ); ut.expect( l_results ).not_to_be_like( '%test_package_3%' ); end; - + + procedure two_test_run_by_two_tags_leg is + l_results clob; + begin + ut3_tester_helper.run_helper.run(a_tags => 'subtest1,subtest2'); + l_results := ut3_tester_helper.main_helper.get_dbms_output_as_clob(); + --Assert + ut.expect( l_results ).to_be_like( '%test_package_1%' ); + ut.expect( l_results ).to_be_like( '%test_package_2%' ); + ut.expect( l_results ).not_to_be_like( '%test_package_1.test2%' ); + ut.expect( l_results ).not_to_be_like( '%test_package_2.test2%' ); + ut.expect( l_results ).not_to_be_like( '%test_package_3%' ); + ut.expect( l_results ).not_to_be_like( '%test_package_3%' ); + end; + procedure suite_with_children_tag is l_results clob; begin @@ -1078,6 +1092,20 @@ Failures:% procedure tag_run_func_path_list is l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests.test_package_1',':tests'),a_tags => 'suite1test1|suite2test1'); + --Assert + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3%' ); + end; + + procedure tag_run_func_path_list_leg is + l_results ut3_develop.ut_varchar2_list; begin l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests.test_package_1',':tests'),a_tags => 'suite1test1,suite2test1'); --Assert @@ -1092,6 +1120,18 @@ Failures:% procedure tag_inc_exc_run_func_path_list is l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests.test_package_1',':tests'),a_tags => '(suite1test1|suite2test1)&!suite2'); + --Assert + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3%' ); + end; + + procedure tag_inc_exc_run_fun_pth_lst_lg is + l_results ut3_develop.ut_varchar2_list; begin l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests.test_package_1',':tests'),a_tags => 'suite1test1,suite2test1,-suite2'); --Assert @@ -1104,6 +1144,22 @@ Failures:% procedure tag_exclude_run_func_path_list is l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests,:tests2'),a_tags => '!suite1test2&!suite2test1&!test1suite3'); + --Assert + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_3%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_3.test2%executed%' ); + end; + +procedure tag_exclude_run_fun_pth_lst_lg is + l_results ut3_develop.ut_varchar2_list; begin l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests,:tests2'),a_tags => '-suite1test2,-suite2test1,-test1suite3'); --Assert @@ -1120,6 +1176,20 @@ Failures:% procedure tag_include_exclude_run_func is l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(a_tags => '(suite1)&(!suite1test2&!suite2test1&!test1suite3)'); + --Assert + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2.test2%executed%' ); + end; + + procedure tag_include_exclude_run_fun_lg is + l_results ut3_develop.ut_varchar2_list; begin l_results := ut3_tester_helper.run_helper.run(a_tags => 'suite1,-suite1test2,-suite2test1,-test1suite3'); --Assert @@ -1132,6 +1202,105 @@ Failures:% ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2.test2%executed%' ); end; + procedure tag_complex_expressions is + l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(a_tags => 'release_3_1_13&(fast|simple)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test6 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => 'release_3_1_13&(!patch_3_1_13&!patch_3_1_14)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_1.test1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test5%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test6%executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => 'release_3_1_13&(patch_3_1_13&!slow)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test6%executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(simple&end_to_end)|(development&fast)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_1.test1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_2.test3 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test6 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test6%executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => 'any'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite2_level1_pkg.test1_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite2_level1_pkg.test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_level1_pkg.test1_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite2_2_level2_pkg.suite2_2_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite2_2_level2_pkg.suite2_2_test2_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_2_level2_pkg.suite1_2_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_2_level2_pkg.suite1_2_test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_1_level2_pkg.suite1_1_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_level1_pkg.test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_1_level2_pkg.suite1_1_test2_level2 executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => 'none'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_level1_pkg.test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_1_level2_pkg.suite1_1_test2_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite2_level1_pkg.test1_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite2_level1_pkg.test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_level1_pkg.test1_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite2_2_level2_pkg.suite2_2_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite2_2_level2_pkg.suite2_2_test2_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_2_level2_pkg.suite1_2_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_2_level2_pkg.suite1_2_test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_1_level2_pkg.suite1_1_test1_level2 executed%' ); + + + end; + + procedure invalid_tag_expression is + l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development!&end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&&end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&end_to_end|)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&!!end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(&development&end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(development|end_to_end))'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + end; + procedure set_application_info is begin dbms_application_info.set_module( gc_module, gc_action ); diff --git a/test/ut3_user/api/test_ut_run.pks b/test/ut3_user/api/test_ut_run.pks index b3be8700d..c57788bff 100644 --- a/test/ut3_user/api/test_ut_run.pks +++ b/test/ut3_user/api/test_ut_run.pks @@ -202,6 +202,9 @@ create or replace package test_ut_run is --%test(Execute tests by passing two tags) procedure two_test_run_by_two_tags; + --%test(Execute tests by passing two tags - Legacy notation) + procedure two_test_run_by_two_tags_leg; + --%test(Execute suite and all of its children) procedure suite_with_children_tag; @@ -235,15 +238,34 @@ create or replace package test_ut_run is --%test(Runs tests from given paths with paths list and a tag) procedure tag_run_func_path_list; + --%test(Runs tests from given paths with paths list and a tag - Legacy Notation) + procedure tag_run_func_path_list_leg; + --%test(Runs tests from given paths with paths list and include/exclude tags) procedure tag_inc_exc_run_func_path_list; + --%test(Runs tests from given paths with paths list and include/exclude tags - Legacy Notation) + procedure tag_inc_exc_run_fun_pth_lst_lg; + --%test(Runs tests from given path and excludes specific tags) procedure tag_exclude_run_func_path_list; + --%test(Runs tests from given path and excludes specific tags - Legacy Notation) + procedure tag_exclude_run_fun_pth_lst_lg; + --%test(Runs tests from given tags and exclude tags) procedure tag_include_exclude_run_func; + --%test(Runs tests from given tags and exclude tags - Legacy Notation) + procedure tag_include_exclude_run_fun_lg; + + --%test(Runs tests suing complex expressions) + procedure tag_complex_expressions; + + --%test(Testing invalid tag expression) + --%throws(-20219) + procedure invalid_tag_expression; + --%endcontext --%context(ut3_info context) diff --git a/test/ut3_user/expectations/test_expectation_anydata.pkb b/test/ut3_user/expectations/test_expectation_anydata.pkb index 98938c107..c70d41f9e 100644 --- a/test/ut3_user/expectations/test_expectation_anydata.pkb +++ b/test/ut3_user/expectations/test_expectation_anydata.pkb @@ -957,6 +957,23 @@ Rows: [ 60 differences, showing first 20 ] ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); end; + procedure collection_not_to_contain is + l_actual ut3_tester_helper.test_dummy_object_list; + l_expected ut3_tester_helper.test_dummy_object_list; + begin + --Arrange + select ut3_tester_helper.test_dummy_object( rownum, 'Something2 '||rownum, rownum+100) + bulk collect into l_actual + from dual connect by level <=4; + select ut3_tester_helper.test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=2 + order by rownum desc; + --Act + ut3_develop.ut.expect(anydata.convertCollection(l_actual)).not_to_contain(anydata.convertCollection(l_expected)); + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); + end; + procedure object_to_contain is begin --Arrange @@ -967,7 +984,7 @@ Rows: [ 60 differences, showing first 20 ] ut3_develop.ut.expect(g_test_actual).to_contain(g_test_expected); ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); end; - + procedure arr_empty_eq_arr_empty_unord is begin --Arrange @@ -1211,5 +1228,32 @@ Rows: [ 60 differences, showing first 20 ] ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); end; + + $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then + procedure long_names_object_types is + pragma autonomous_transaction; + begin + execute immediate q'[create or replace type + very_long_type_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_did_it + is object ( + code number(18) + )]'; + execute immediate q'[ + begin + ut3_develop.ut.expect(anydata.convertObject( + very_long_type_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_did_it(1) + )).to_equal(anydata.convertObject( + very_long_type_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_did_it(1) + )); + end;]'; + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); + execute immediate 'drop type very_long_type_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_did_it'; + exception + when others then + execute immediate 'drop type very_long_type_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_did_it'; + raise; + end; + $end + end; / \ No newline at end of file diff --git a/test/ut3_user/expectations/test_expectation_anydata.pks b/test/ut3_user/expectations/test_expectation_anydata.pks index 2abb48b7f..54c246bd3 100644 --- a/test/ut3_user/expectations/test_expectation_anydata.pks +++ b/test/ut3_user/expectations/test_expectation_anydata.pks @@ -195,6 +195,9 @@ create or replace package test_expectation_anydata is --%test( Success when anydata collection contains data from another anydata collection) procedure collection_to_contain; + --%test( Success when anydata collection not contains data from another anydata collection) + procedure collection_not_to_contain; + --%test( Success when anydata object contains data from another anydata) procedure object_to_contain; @@ -233,5 +236,11 @@ create or replace package test_expectation_anydata is --%test ( Reports success when comparing complex nested objects ) procedure complex_nested_object_success; + + $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then + --%test ( Compares object types with long names - Issue #1235 ) + procedure long_names_object_types; + $end + end; / diff --git a/test/ut3_user/expectations/test_expectations_cursor.pkb b/test/ut3_user/expectations/test_expectations_cursor.pkb index 952084fd3..a4ef4e8f2 100644 --- a/test/ut3_user/expectations/test_expectations_cursor.pkb +++ b/test/ut3_user/expectations/test_expectations_cursor.pkb @@ -2329,6 +2329,23 @@ Diff:% ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); end; + procedure cursor_not_to_contain2 + as + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + open l_expected for select 'TEST' username, -600 user_id from dual; + + open l_actual for select username, user_id from all_users + union all + select 'TEST1' username, -601 user_id from dual; + + --Act + ut3_develop.ut.expect(l_actual).not_to(ut3_develop.contain(l_expected)); + --Assert + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); + end; + procedure cursor_not_to_contain_fail is l_actual sys_refcursor; l_expected sys_refcursor; @@ -2359,6 +2376,37 @@ Diff:% ut.expect(l_actual_message).to_be_like(l_expected_message); end; + + procedure cursor_not_to_contain_fail2 is + l_actual sys_refcursor; + l_expected sys_refcursor; + l_expected_message varchar2(32767); + l_actual_message varchar2(32767); + begin + --Arrange + open l_expected for select 'TEST' username, -600 user_id from dual; + + open l_actual for select username, user_id from all_users + union all + select 'TEST' username, -600 user_id from dual; + + --Act + ut3_develop.ut.expect(l_actual).not_to(ut3_develop.contain(l_expected)); + --Assert + l_expected_message := q'[%Actual: (refcursor [ count = % ])% +%Data-types:% +%VARCHAR2NUMBER% +%Data:% +%was expected not to contain:(refcursor [ count = 1 ])% +%Data-types:% +%CHARNUMBER% +%Data:% +%TEST-600%]'; + l_actual_message := ut3_tester_helper.main_helper.get_failed_expectations(1); + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + procedure cursor_not_to_contain_joinby is l_actual sys_refcursor; l_expected sys_refcursor; @@ -2372,7 +2420,21 @@ Diff:% --Assert ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); end; - + + procedure cursor_not_to_contain_joinby2 is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select username,rownum * 10 user_id from all_users where rownum < 5; + open l_expected for select username||to_char(rownum) username ,rownum user_id from all_users where rownum < 5; + + --Act + ut3_develop.ut.expect(l_actual).not_to(ut3_develop.contain(l_expected).join_by('USER_ID')); + --Assert + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); + end; + procedure not_cont_join_incl_cols_as_lst is l_actual sys_refcursor; l_expected sys_refcursor; @@ -2386,6 +2448,19 @@ Diff:% ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); end; + procedure not_con_join_incl_cols_as_lst2 is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn, 'b' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 10; + open l_expected for select rownum * 20 rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; + --Act + ut3_develop.ut.expect(l_actual).not_to(ut3_develop.contain(l_expected).include(ut3_develop.ut_varchar2_list('RN','//A_Column','SOME_COL')).join_by('RN')); + --Assert + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); + end; + procedure not_cont_join_excl_cols_as_lst is l_actual sys_refcursor; l_expected sys_refcursor; @@ -2399,6 +2474,19 @@ Diff:% ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); end; + procedure not_con_join_excl_cols_as_lst2 is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'y' SOME_COL, 'd' "Some_Col" from dual a connect by level < 10; + open l_expected for select rownum * 20 as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; + --Act + ut3_develop.ut.expect(l_actual).not_to(ut3_develop.contain(l_expected).exclude(ut3_develop.ut_varchar2_list('//Some_Col','A_COLUMN')).join_by('RN')); + --Assert + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); + end; + procedure to_contain_duplicates is l_actual sys_refcursor; l_expected sys_refcursor; @@ -2440,6 +2528,16 @@ Diff:% ut.expect(l_actual_message).to_be_like(l_expected_message); end; + procedure to_not_contain_fails_1245 is + c1 sys_refcursor; + c2 sys_refcursor; + begin + open c1 for select 'a' as letter from dual union all select 'b' from dual; + open c2 for select 'c' as letter from dual; + ut3_develop.ut.expect(c1).not_to(ut3_develop.contain(c2)); + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_equal(0); + end; + procedure udt_messg_format_eq is l_actual sys_refcursor; l_expected sys_refcursor; @@ -2852,5 +2950,47 @@ Rows: [ 2 differences ] ); end; + procedure cr_joinby_compare_issue_1293 is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_expected for + select 'FOO' username, 12 from dual union all + select 'TEST' username, -600 user_id from dual + order by 1 desc; + open l_actual for + select 'FOO' username, 12 from dual union all + select 'TEST' username, -600 user_id from dual union all + -- DUPLICATE!!! + select 'TEST' username, -600 user_id from dual + order by 1 asc; + --Act + ut3_develop.ut.expect(l_actual).to_equal(l_expected).join_by('USERNAME'); + --Assert + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_be_greater_than(0); + end; + + procedure cr_not_joinby_comp_issue_1293 is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_expected for + select 'FOO' username, 12 from dual union all + select 'TEST' username, -600 user_id from dual + order by 1 desc; + open l_actual for + select 'FOO' username, 12 from dual union all + select 'TEST' username, -600 user_id from dual union all + -- DUPLICATE!!! + select 'TEST' username, -600 user_id from dual + order by 1 asc; + --Act + ut3_develop.ut.expect(l_actual).to_equal(l_expected); + --Assert + ut.expect(ut3_tester_helper.main_helper.get_failed_expectations_num).to_be_greater_than(0); + end; + end; / diff --git a/test/ut3_user/expectations/test_expectations_cursor.pks b/test/ut3_user/expectations/test_expectations_cursor.pks index 0ed249a49..8cceb5e01 100644 --- a/test/ut3_user/expectations/test_expectations_cursor.pks +++ b/test/ut3_user/expectations/test_expectations_cursor.pks @@ -376,24 +376,42 @@ create or replace package test_expectations_cursor is --%test( Cursor not to contains data from another cursor) procedure cursor_not_to_contain; + --%test( Cursor not_to[contain] data from another cursor) + procedure cursor_not_to_contain2; + --%test( Cursor fail not to contains data from another cursor) procedure cursor_not_to_contain_fail; - + + --%test( Cursor fail not_to[contain] data from another cursor) + procedure cursor_not_to_contain_fail2; + --%test( Cursor not contains data from another cursor with joinby clause) procedure cursor_not_to_contain_joinby; + --%test( Cursor not_to[contain] data from another cursor with joinby clause) + procedure cursor_not_to_contain_joinby2; + --%test(Cursor not contains data with of columns to include and join by value) procedure not_cont_join_incl_cols_as_lst; + --%test(Cursor not_to[contain] data with of columns to include and join by value) + procedure not_con_join_incl_cols_as_lst2; + --%test(Cursor not contains data with of columns to exclude and join by value) procedure not_cont_join_excl_cols_as_lst; + --%test(Cursor not_to[contain] data with of columns to exclude and join by value) + procedure not_con_join_excl_cols_as_lst2; + --%test(Cursor to contain duplicates) procedure to_contain_duplicates; --%test(Cursor to contain duplicates fail) procedure to_contain_duplicates_fail; - + + --%test(Cursor using not_to[contain] fails #1245) + procedure to_not_contain_fails_1245; + --%test(Display a message with a uer defined type with only type name not structure on equal) procedure udt_messg_format_eq; @@ -467,5 +485,11 @@ create or replace package test_expectations_cursor is --%test( Multiple failures reported correctly - Issue #998 ) procedure multiple_cursor_expectations; + --%test( Compares cursors with duplicate rows using join by - Issue #1293 ) + procedure cr_joinby_compare_issue_1293; + + --%test( Compares cursors with duplicate rows - Issue #1293 ) + procedure cr_not_joinby_comp_issue_1293; + end; / diff --git a/test/ut3_user/reporters.pkb b/test/ut3_user/reporters.pkb index b6d43b144..031683615 100644 --- a/test/ut3_user/reporters.pkb +++ b/test/ut3_user/reporters.pkb @@ -90,11 +90,10 @@ as procedure erroring_test is - l_variable integer; + l_integer_variable integer; begin dbms_output.put_line(''); - l_variable := 'a string'; - ut3_develop.ut.expect(l_variable).to_equal(1); + l_integer_variable := 'a string'; end; procedure disabled_test diff --git a/test/ut3_user/reporters/test_coverage/test_cov_cobertura_reporter.pkb b/test/ut3_user/reporters/test_coverage/test_cov_cobertura_reporter.pkb index 2c36f37ba..ad6c9dbb4 100644 --- a/test/ut3_user/reporters/test_coverage/test_cov_cobertura_reporter.pkb +++ b/test/ut3_user/reporters/test_coverage/test_cov_cobertura_reporter.pkb @@ -25,9 +25,9 @@ create or replace package body test_cov_cobertura_reporter is ]'||l_file_path||q'[ - + - + ]'||l_block_cov||q'[ diff --git a/test/ut3_user/reporters/test_coverage/test_coverage_standalone.pkb b/test/ut3_user/reporters/test_coverage/test_coverage_standalone.pkb index e5706f885..90cfb484e 100644 --- a/test/ut3_user/reporters/test_coverage/test_coverage_standalone.pkb +++ b/test/ut3_user/reporters/test_coverage/test_coverage_standalone.pkb @@ -20,9 +20,9 @@ create or replace package body test_coverage_standalone is ]'||l_file_path||q'[ - + - + ]'||l_block_cov||q'[ diff --git a/test/ut3_user/reporters/test_coverage/test_extended_coverage.pkb b/test/ut3_user/reporters/test_coverage/test_extended_coverage.pkb index 3c8781bc4..d7f1b61d7 100644 --- a/test/ut3_user/reporters/test_coverage/test_extended_coverage.pkb +++ b/test/ut3_user/reporters/test_coverage/test_extended_coverage.pkb @@ -112,10 +112,10 @@ create or replace package body test_extended_coverage is l_actual clob; begin --Arrange - l_expected := '%' || - '%%'; - l_not_expected := '%' || - '%%'; + l_expected := '%' || + '%%'; + l_not_expected := '%' || + '%%'; --Act l_actual := ut3_tester_helper.coverage_helper.run_tests_as_job( @@ -123,14 +123,17 @@ create or replace package body test_extended_coverage is ut3_develop.ut.run( a_paths => ut3_develop.ut_varchar2_list('ut3_develop.test_regex_dummy_cov', 'ut3_tester_helper.test_regex_dummy_cov'), a_reporter=> ut3_develop.ut_coverage_sonar_reporter( ), - a_include_schema_expr => '^ut3_develop', - a_include_objects => ut3_develop.ut_varchar2_list( 'ut3_tester_helper.regex_dummy_cov' ) + a_include_schema_expr => '^ut3_tester_hel.*', + a_include_objects => ut3_develop.ut_varchar2_list( 'ut3_develop.regex_dummy_cov' ) ) ]' ); + --Assert - ut.expect(l_actual).to_be_like(l_expected); + --The below is a workaround for problem with large CLOB like comparison on 11g XE db. + ut.expect(to_char(substr(l_actual,instr(l_actual,''),2000))).to_be_like(l_expected); ut.expect(l_actual).not_to_be_like(l_not_expected); + ut.expect(l_actual).not_to_be_like('%ut3_tester_helper.test_regex_dummy_cov%'); end; procedure coverage_regex_include_object is @@ -140,9 +143,9 @@ create or replace package body test_extended_coverage is begin --Arrange l_expected := '%' || - '%%'; + '%%'; l_not_expected := '%' || - '%%'; + '%%'; --Act l_actual := ut3_tester_helper.coverage_helper.run_tests_as_job( @@ -167,9 +170,9 @@ create or replace package body test_extended_coverage is begin --Arrange l_expected := '%' || - '%%'; + '%%'; l_not_expected := '%' || - '%%'; + '%%'; --Act l_actual := ut3_tester_helper.coverage_helper.run_tests_as_job( @@ -194,9 +197,9 @@ create or replace package body test_extended_coverage is begin --Arrange l_expected := '%' || - '%%'; + '%%'; l_not_expected := '%' || - '%%'; + '%%'; --Act l_actual := ut3_tester_helper.coverage_helper.run_tests_as_job( diff --git a/test/ut3_user/reporters/test_coverage/test_html_coverage_reporter.pkb b/test/ut3_user/reporters/test_coverage/test_html_coverage_reporter.pkb index d1645acae..2f2200116 100644 --- a/test/ut3_user/reporters/test_coverage/test_html_coverage_reporter.pkb +++ b/test/ut3_user/reporters/test_coverage/test_html_coverage_reporter.pkb @@ -32,5 +32,70 @@ create or replace package body test_html_coverage_reporter is ut.expect(l_actual).to_be_like(l_expected); end; + procedure setup_long_lines is + pragma autonomous_transaction; + begin + + execute immediate q'[create or replace type string_array is table of varchar2(5 char);]'; + execute immediate q'[ + create or replace function f return integer is + l_string_array string_array; + l_count integer; + begin + -- line is 1912 chars long, 1911 characters seem to be the max. line length that works (@formatter:off) + l_string_array := string_array('aahed', 'aalii', 'aargh', 'aarti', 'abaca', 'abaci', 'abacs', 'abaft', 'abaka', 'abamp', 'aband', 'abash', 'abask', 'abaya', 'abbas', 'abbed', 'abbes', 'abcee', 'abeam', 'abear', 'abele', 'abers', 'abets', 'abies', 'abler', 'ables', 'ablet', 'ablow', 'abmho', 'abohm', 'aboil', 'aboma', 'aboon', 'abord', 'abore', 'abram', 'abray', 'abrim', 'abrin', 'abris', 'absey', 'absit', 'abuna', 'abune', 'abuts', 'abuzz', 'abyes', 'abysm', 'acais', 'acari', 'accas', 'accoy', 'acerb', 'acers', 'aceta', 'achar', 'ached', 'aches', 'achoo', 'acids', 'acidy', 'acing', 'acini', 'ackee', 'acker', 'acmes', 'acmic', 'acned', 'acnes', 'acock', 'acold', 'acred', 'acres', 'acros', 'acted', 'actin', 'acton', 'acyls', 'adaws', 'adays', 'adbot', 'addax', 'added', 'adder', 'addio', 'addle', 'adeem', 'adhan', 'adieu', 'adios', 'adits', 'adman', 'admen', 'admix', 'adobo', 'adown', 'adoze', 'adrad', 'adred', 'adsum', 'aduki', 'adunc', 'adust', 'advew', 'adyta', 'adzed', 'adzes', 'aecia', 'aedes', 'aegis', 'aeons', 'aerie', 'aeros', 'aesir', 'afald', 'afara', 'afars', 'afear', 'aflaj', 'afore', 'afrit', 'afros', 'agama', 'agami', 'agars', 'agast', 'agave', 'agaze', 'agene', 'agers', 'agger', 'aggie', 'aggri', 'aggro', 'aggry', 'aghas', 'agila', 'agios', 'agism', 'agist', 'agita', 'aglee', 'aglet', 'agley', 'agloo', 'aglus', 'agmas', 'agoge', 'agone', 'agons', 'agood', 'agora', 'agria', 'agrin', 'agros', 'agued', 'agues', 'aguna', 'aguti', 'aheap', 'ahent', 'ahigh', 'ahind', 'ahing', 'ahint', 'ahold', 'ahull', 'ahuru', 'aidas', 'aided', 'aides', 'aidoi', 'aidos', 'aiery', 'aigas', 'aight', 'ailed', 'aimed', 'aimer', 'ainee', 'ainga', 'aioli', 'aired', 'airer', 'airns', 'airth', 'airts', 'aitch', 'aitus', 'aiver', 'aiyee', 'aizle', 'ajies', 'ajiva', 'ajuga', 'ajwan', 'akees', 'akela', 'akene', 'aking', 'akita', 'akkas', 'alaap', 'alack', 'alamo', 'aland', 'alane', 'alang', 'a'); + select count(*) into l_count from table(l_string_array); + return l_count; + end;]'; + + execute immediate q'[ + create or replace package test_f is + --%suite + + --%test + procedure fail_ut_coverage_html_reporter; + end;]'; + + execute immediate q'[ + create or replace package body test_f is + procedure fail_ut_coverage_html_reporter is + begin + ut3_develop.ut.expect(f()).to_be_greater_or_equal(1); + end; + end; + ]'; + end; + + procedure cleanup_long_lines is + pragma autonomous_transaction; + begin + execute immediate 'drop package test_f'; + execute immediate 'drop function f'; + execute immediate 'drop type string_array force'; + end; + + procedure report_long_lines is + l_expected varchar2(32767); + l_actual clob; + l_name varchar2(250); + begin + --Arrange + l_expected := '%l_string_array := string_array%'; + + l_actual := + ut3_tester_helper.coverage_helper.run_tests_as_job( + q'[ + ut3_develop.ut.run( + a_path => 'ut3_user.test_f', + a_reporter=> ut3_develop.ut_coverage_html_reporter(), + a_include_objects => ut3_develop.ut_varchar2_list( 'UT3_USER.F' ) + ) + ]' + ); + --Assert + ut.expect(l_actual).to_be_like(l_expected); + end; + + end test_html_coverage_reporter; / diff --git a/test/ut3_user/reporters/test_coverage/test_html_coverage_reporter.pks b/test/ut3_user/reporters/test_coverage/test_html_coverage_reporter.pks index 629d41793..a11f3e912 100644 --- a/test/ut3_user/reporters/test_coverage/test_html_coverage_reporter.pks +++ b/test/ut3_user/reporters/test_coverage/test_html_coverage_reporter.pks @@ -6,5 +6,13 @@ create or replace package test_html_coverage_reporter is --%test(reports on a project file mapped to database object in extended profiler coverage) procedure report_on_file; + procedure setup_long_lines; + procedure cleanup_long_lines; + + --%test(reports on lines exceeding 4000 chars after conversion to XML) + --%beforetest(setup_long_lines) + --%aftertest(cleanup_long_lines) + procedure report_long_lines; + end test_html_coverage_reporter; / diff --git a/test/ut3_user/reporters/test_coverage/test_proftab_coverage.pkb b/test/ut3_user/reporters/test_coverage/test_proftab_coverage.pkb index 7cad0a67e..f3aad5702 100644 --- a/test/ut3_user/reporters/test_coverage/test_proftab_coverage.pkb +++ b/test/ut3_user/reporters/test_coverage/test_proftab_coverage.pkb @@ -66,6 +66,7 @@ create or replace package body test_proftab_coverage is ); --Assert ut.expect(l_actual).to_be_like(l_expected); + ut.expect(l_actual).not_to_be_like('%%'); end; procedure coverage_for_file is diff --git a/test/ut3_user/reporters/test_documentation_reporter.pkb b/test/ut3_user/reporters/test_documentation_reporter.pkb index 6ace70c49..016cec256 100644 --- a/test/ut3_user/reporters/test_documentation_reporter.pkb +++ b/test/ut3_user/reporters/test_documentation_reporter.pkb @@ -34,10 +34,9 @@ Failures: "Fails as values are different" Actual: 'number [1] ' (varchar2) was expected to equal: 'number [2] ' (varchar2)% at "UT3_USER.TEST_REPORTERS%", line 36 ut3_develop.ut.expect('number [1] ','Fails as values are different').to_equal('number [2] '); -% % 2) erroring_test - ORA-06502: PL/SQL: numeric or value error: character to number conversion error + ORA-06502: PL/SQL: %: character to number conversion error ORA-06512: at "UT3_USER.TEST_REPORTERS", line 44% ORA-06512: at line 6 Finished in % seconds diff --git a/test/ut3_user/reporters/test_realtime_reporter.pkb b/test/ut3_user/reporters/test_realtime_reporter.pkb index 2dafa71d0..84053c3c9 100644 --- a/test/ut3_user/reporters/test_realtime_reporter.pkb +++ b/test/ut3_user/reporters/test_realtime_reporter.pkb @@ -408,7 +408,7 @@ create or replace package body test_realtime_reporter as from table(g_events) t where t.event_doc.extract('/event[@type="post-test"]/test/@id').getstringval() = 'realtime_reporting.check_realtime_reporting3.test_6_with_runtime_error'; - ut3_tester_helper.main_helper.append_to_list(l_expected_list, ''); l_expected := ut3_tester_helper.main_helper.table_to_clob(l_expected_list); diff --git a/test/ut3_user/reporters/test_teamcity_reporter.pkb b/test/ut3_user/reporters/test_teamcity_reporter.pkb index ff550e488..517ddc037 100644 --- a/test/ut3_user/reporters/test_teamcity_reporter.pkb +++ b/test/ut3_user/reporters/test_teamcity_reporter.pkb @@ -31,12 +31,37 @@ create or replace package body test_teamcity_reporter as end; end;]'; + execute immediate q'[create or replace package check_multiple_failures is + --%suite + + --%test + procedure multi_failure; + + --%test + procedure multi_failure_on_error; + end;]'; + execute immediate q'[create or replace package body check_multiple_failures is + procedure multi_failure is + begin + ut3_develop.ut.expect(1).to_be_null; + ut3_develop.ut.expect(2).to_equal(1); + ut3_develop.ut.expect('Bad').to_equal('Good'); + end; + procedure multi_failure_on_error is + l_integer_variable integer; + begin + ut3_develop.ut.expect(1).to_be_null; + ut3_develop.ut.expect(2).to_equal(1); + ut3_develop.ut.expect('Bad').to_equal('Good'); + l_integer_variable := 'a string'; + end; + end;]'; + end; procedure report_produces_expected_out is l_output_data ut3_develop.ut_varchar2_list; - l_output clob; l_expected varchar2(32767); begin l_expected := q'{%##teamcity[testSuiteStarted timestamp='%' name='org'] @@ -63,8 +88,8 @@ create or replace package body test_teamcity_reporter as -%##teamcity[testStdErr timestamp='%' name='ut3_user.test_reporters.erroring_test' out='Test exception:|nORA-06502: PL/SQL: numeric or value error: character to number conversion error|nORA-06512: at "UT3_USER.TEST_REPORTERS", line %|nORA-06512: at %|n'] -%##teamcity[testFailed timestamp='%' details='Test exception:|nORA-06502: PL/SQL: numeric or value error: character to number conversion error|nORA-06512: at "UT3_USER.TEST_REPORTERS", line %|nORA-06512: at %|n' message='Error occured' name='ut3_user.test_reporters.erroring_test'] +%##teamcity[testStdErr timestamp='%' name='ut3_user.test_reporters.erroring_test' out='Test exception:|nORA-06502: PL/SQL: %: character to number conversion error|nORA-06512: at "UT3_USER.TEST_REPORTERS", line %|nORA-06512: at %|n'] +%##teamcity[testFailed timestamp='%' details='Test exception:|nORA-06502: PL/SQL: %: character to number conversion error|nORA-06512: at "UT3_USER.TEST_REPORTERS", line %|nORA-06512: at %|n' message='Error occured' name='ut3_user.test_reporters.erroring_test'] %##teamcity[testFinished timestamp='%' duration='%' name='ut3_user.test_reporters.erroring_test'] %##teamcity[testStarted timestamp='%' captureStandardOutput='true' name='ut3_user.test_reporters.disabled_test'] %##teamcity[testIgnored timestamp='%' name='ut3_user.test_reporters.disabled_test'] @@ -84,7 +109,6 @@ create or replace package body test_teamcity_reporter as procedure escape_special_chars is l_output_data ut3_develop.ut_varchar2_list; - l_output clob; l_expected varchar2(32767); begin l_expected := q'{%##teamcity[testSuiteStarted timestamp='%' name='A suite with |'quote|''] @@ -103,7 +127,6 @@ create or replace package body test_teamcity_reporter as procedure trims_long_output is l_output_data ut3_develop.ut_varchar2_list; - l_output clob; l_expected varchar2(32767); begin l_expected := q'{%##teamcity[testSuiteStarted timestamp='%' name='check_trims_long_output'] @@ -120,11 +143,54 @@ create or replace package body test_teamcity_reporter as ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); end; + procedure report_multiple_expectations is + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'{%##teamcity[testSuiteStarted timestamp='%' name='check_multiple_failures'] +%##teamcity[testStarted timestamp='%' captureStandardOutput='true' name='ut3_user.check_multiple_failures.multi_failure'] +%##teamcity[testFailed timestamp='%' details='Actual: 1 (number) was expected to be null' name='ut3_user.check_multiple_failures.multi_failure'] +%##teamcity[testFailed timestamp='%' details='Actual: 2 (number) was expected to equal: 1 (number)' name='ut3_user.check_multiple_failures.multi_failure'] +%##teamcity[testFailed timestamp='%' details='Actual: |'Bad|' (varchar2) was expected to equal: |'Good|' (varchar2)' name='ut3_user.check_multiple_failures.multi_failure'] +%##teamcity[testFinished timestamp='%' duration='%' name='ut3_user.check_multiple_failures.multi_failure'] +%##teamcity[testSuiteFinished timestamp='%' name='check_multiple_failures']}'; + --act + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('check_multiple_failures.multi_failure',ut3_develop.ut_teamcity_reporter())); + + --assert + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); + end; + + procedure report_multiple_expect_on_err is + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'{%##teamcity[testSuiteStarted timestamp='%' name='check_multiple_failures'] +%##teamcity[testStarted timestamp='%' captureStandardOutput='true' name='ut3_user.check_multiple_failures.multi_failure_on_error'] +%##teamcity[testStdErr timestamp='%' name='ut3_user.check_multiple_failures.multi_failure_on_error' out='Test exception:|nORA-06502: PL/SQL: %: character to number conversion error|nORA-06512: at "UT3_USER.CHECK_MULTIPLE_FAILURES", line %|nORA-06512: at %|n'] +%##teamcity[testFailed timestamp='%' details='Test exception:|nORA-06502: PL/SQL: %: character to number conversion error|nORA-06512: at "UT3_USER.CHECK_MULTIPLE_FAILURES", line %|nORA-06512: at %|n' message='Error occured' name='ut3_user.check_multiple_failures.multi_failure_on_error'] +%##teamcity[testFailed timestamp='%' details='Actual: 1 (number) was expected to be null' name='ut3_user.check_multiple_failures.multi_failure_on_error'] +%##teamcity[testFailed timestamp='%' details='Actual: 2 (number) was expected to equal: 1 (number)' name='ut3_user.check_multiple_failures.multi_failure_on_error'] +%##teamcity[testFailed timestamp='%' details='Actual: |'Bad|' (varchar2) was expected to equal: |'Good|' (varchar2)' name='ut3_user.check_multiple_failures.multi_failure_on_error'] +%##teamcity[testFinished timestamp='%' duration='%' name='ut3_user.check_multiple_failures.multi_failure_on_error'] +%##teamcity[testSuiteFinished timestamp='%' name='check_multiple_failures']}'; + --act + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('check_multiple_failures.multi_failure_on_error',ut3_develop.ut_teamcity_reporter())); + + --assert + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); + end; + procedure remove_test_package is pragma autonomous_transaction; begin execute immediate 'drop package check_escape_special_chars'; execute immediate 'drop package check_trims_long_output'; + execute immediate 'drop package check_multiple_failures'; end; end; diff --git a/test/ut3_user/reporters/test_teamcity_reporter.pks b/test/ut3_user/reporters/test_teamcity_reporter.pks index f849751f1..1b9277e7a 100644 --- a/test/ut3_user/reporters/test_teamcity_reporter.pks +++ b/test/ut3_user/reporters/test_teamcity_reporter.pks @@ -15,6 +15,12 @@ create or replace package test_teamcity_reporter as --%test(Trims output so it fits into 4000 chars) procedure trims_long_output; + --%test(Reports failures on multiple expectations) + procedure report_multiple_expectations; + + --%test(Reports failures on multiple expectations) + procedure report_multiple_expect_on_err; + --%afterall procedure remove_test_package; diff --git a/test/ut3_user/reporters/test_tfs_junit_reporter.pkb b/test/ut3_user/reporters/test_tfs_junit_reporter.pkb index 7eb4f8ab6..1036be2ba 100644 --- a/test/ut3_user/reporters/test_tfs_junit_reporter.pkb +++ b/test/ut3_user/reporters/test_tfs_junit_reporter.pkb @@ -8,7 +8,7 @@ create or replace package body test_tfs_junit_reporter as --%test(A test with ) procedure test_do_stuff; - + end;]'; execute immediate q'[create or replace package body check_junit_reporting is procedure test_do_stuff is @@ -18,12 +18,12 @@ create or replace package body test_tfs_junit_reporter as end; end;]'; - + execute immediate q'[create or replace package check_junit_rep_suitepath is --%suitepath(core) --%suite(check_junit_rep_suitepath) --%displayname(Check JUNIT Get path for suitepath) - + --%test(check_junit_rep_suitepath) --%displayname(Check JUNIT Get path for suitepath) procedure check_junit_rep_suitepath; @@ -38,7 +38,7 @@ create or replace package body test_tfs_junit_reporter as execute immediate q'[create or replace package check_junit_flat_suitepath is --%suitepath(core.check_junit_rep_suitepath) --%suite(flatsuitepath) - + --%beforeall procedure donuffin; end;]'; @@ -49,8 +49,85 @@ create or replace package body test_tfs_junit_reporter as end; end;]'; - end; + execute immediate q'[create or replace package check_junit_in_context is + --%suitepath(core.check_junit_rep_suitepath) + --%suite(inctxsuite) + --%displayname(JUNIT test are inside context) + + -- %context(incontext) + -- %name(incontext) + + --%test(incontext) + --%displayname(Check JUNIT Get path incontext) + procedure check_junit_rep_incontext; + + -- %endcontext + end;]'; + execute immediate q'[create or replace package body check_junit_in_context is + procedure check_junit_rep_incontext is + begin + ut3_develop.ut.expect(1).to_equal(1); + end; + end;]'; + + execute immediate q'[create or replace package check_junit_out_context is + --%suitepath(core) + --%suite(outctxsuite) + --%displayname(JUNIT test are outside context) + + -- %context(outcontext) + -- %name(outcontext) + + -- %endcontext + + + --%test(outctx) + --%displayname(outctx) + procedure outctx; + + + end;]'; + execute immediate q'[create or replace package body check_junit_out_context is + procedure outctx is + begin + ut3_develop.ut.expect(1).to_equal(1); + end; + end;]'; + + execute immediate q'[create or replace package check_junit_inout_context is + --%suitepath(core) + --%suite(inoutcontext) + --%displayname(Test in and out of context) + -- %context(incontext) + -- %name(ProductincontextFeatures) + + --%test(inctx) + --%displayname(inctx) + procedure inctx; + + -- %endcontext + + + --%test(outctx) + --%displayname(outctx) + procedure outctx; + + + end;]'; + execute immediate q'[create or replace package body check_junit_inout_context is + procedure inctx is + begin + ut3_develop.ut.expect(1).to_equal(1); + end; + + procedure outctx is + begin + ut3_develop.ut.expect(1).to_equal(1); + end; + end;]'; + + end; procedure escapes_special_chars is l_results ut3_develop.ut_varchar2_list; @@ -92,7 +169,7 @@ create or replace package body test_tfs_junit_reporter as --Assert ut.expect(l_actual).to_be_like('%testcase classname="check_junit_reporting"%'); end; - + procedure check_flatten_nested_suites is l_results ut3_develop.ut_varchar2_list; l_actual clob; @@ -111,7 +188,7 @@ create or replace package body test_tfs_junit_reporter as
    %'); end; - + procedure check_nls_number_formatting is l_results ut3_develop.ut_varchar2_list; l_actual clob; @@ -163,5 +240,73 @@ create or replace package body test_tfs_junit_reporter as reporters.check_xml_encoding_included(ut3_develop.ut_tfs_junit_reporter(), 'UTF-8'); end; + procedure reports_only_test_in_ctx is + l_results ut3_develop.ut_varchar2_list; + l_actual clob; + begin + --Act + select * + bulk collect into l_results + from table(ut3_develop.ut.run('check_junit_in_context',ut3_develop.ut_tfs_junit_reporter())); + l_actual := ut3_tester_helper.main_helper.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_be_like(' + + + + + + + + +%'); + end; + + procedure reports_only_test_out_ctx is + l_results ut3_develop.ut_varchar2_list; + l_actual clob; + begin + --Act + select * + bulk collect into l_results + from table(ut3_develop.ut.run('check_junit_out_context',ut3_develop.ut_tfs_junit_reporter())); + l_actual := ut3_tester_helper.main_helper.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_be_like(' + + + + + + + + +%'); + end; + + procedure reports_only_test_inout_ctx is + l_results ut3_develop.ut_varchar2_list; + l_actual clob; + begin + --Act + select * + bulk collect into l_results + from table(ut3_develop.ut.run('check_junit_inout_context',ut3_develop.ut_tfs_junit_reporter())); + l_actual := ut3_tester_helper.main_helper.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_be_like(' + + + + + + + + + + +%'); + end; + end; -/ +/ \ No newline at end of file diff --git a/test/ut3_user/reporters/test_tfs_junit_reporter.pks b/test/ut3_user/reporters/test_tfs_junit_reporter.pks index 07acb4d21..70f78e334 100644 --- a/test/ut3_user/reporters/test_tfs_junit_reporter.pks +++ b/test/ut3_user/reporters/test_tfs_junit_reporter.pks @@ -30,6 +30,15 @@ create or replace package test_tfs_junit_reporter as --%test(Includes XML header with encoding when encoding provided) procedure check_encoding_included; + --%test(Reports only testsuites where there are any testcases, all tests are in context) + procedure reports_only_test_in_ctx; + + --%test(Reports only testsuites where there are any testcases, all tests are outside context) + procedure reports_only_test_out_ctx; + + --%test(Reports only testsuites where there are any testcases, one test in ctx one test outside) + procedure reports_only_test_inout_ctx; + --%afterall procedure remove_test_package; end;