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/DISCUSSION_TEMPLATE/bug-triage.yml b/.github/DISCUSSION_TEMPLATE/bug-triage.yml new file mode 100644 index 000000000..7a5da8d73 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/bug-triage.yml @@ -0,0 +1,69 @@ +title: "[Bug?] " +labels: ["bug-triage"] +body: + - type: markdown + attributes: + value: | + Use this category if you think you have found a bug in the utPLSQL framework and want to discuss it before a formal issue is opened. + + Once a maintainer confirms the bug, they will open an issue and link it back here. You do not need to open an issue yourself. + + If you are certain it is a bug and can provide a reliable reproduction, you may open an issue directly instead. + + - type: input + id: utplsql-version + attributes: + label: utPLSQL version + placeholder: "e.g. 3.1.13" + validations: + required: true + + - type: input + id: oracle-version + attributes: + label: Oracle Database version + placeholder: "e.g. 19c, 21c" + validations: + required: true + + - type: input + id: os + attributes: + label: Operating system + placeholder: "e.g. Linux, Windows Server 2019" + validations: + required: false + + - type: textarea + id: description + attributes: + label: What did you observe? + description: Describe the unexpected behaviour. Include any error messages or stack traces. + render: text + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What did you expect? + description: Describe what you expected to happen instead. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Reproduction case + description: Paste the minimal PL/SQL needed to reproduce the problem. The simpler, the better. + render: sql + validations: + required: false + + - type: textarea + id: investigation + attributes: + label: What have you investigated so far? + description: Any findings from your own debugging that might help narrow this down. + validations: + required: false diff --git a/.github/DISCUSSION_TEMPLATE/contributing.yml b/.github/DISCUSSION_TEMPLATE/contributing.yml new file mode 100644 index 000000000..31a213cbb --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/contributing.yml @@ -0,0 +1,43 @@ +title: "" +labels: ["contributing"] +body: + - type: markdown + attributes: + value: | + Use this category if you are working on or considering a contribution to the utPLSQL framework codebase and have questions about the internals, development setup, testing approach, or contribution process. + + Before posting, it is worth checking: + - [CONTRIBUTING.md](https://github.com/utPLSQL/utPLSQL/blob/develop/CONTRIBUTING.md) + - [Development setup guide](https://github.com/utPLSQL/utPLSQL/blob/develop/development/README.md) + + - type: dropdown + id: topic + attributes: + label: Topic + options: + - Development environment setup + - Understanding the codebase + - Test writing and running locally + - CI / build pipeline + - Pull request process + - Architectural question + - Other + validations: + required: true + + - type: textarea + id: question + attributes: + label: Question or topic + description: Describe what you are working on and what you need help with. + validations: + required: true + + - type: textarea + id: context + attributes: + label: Relevant code or context + description: Paste any relevant code, configuration, or error output here. + render: sql + validations: + required: false diff --git a/.github/DISCUSSION_TEMPLATE/feature-requests.yml b/.github/DISCUSSION_TEMPLATE/feature-requests.yml new file mode 100644 index 000000000..b07a043b5 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/feature-requests.yml @@ -0,0 +1,61 @@ +title: "[Feature] " +labels: ["feature-request"] +body: + - type: markdown + attributes: + value: | + Use this category for feature ideas specific to the utPLSQL framework: new matchers, annotation behaviour, reporter output, suite control, coverage, or installation. + + > For ideas that affect multiple components or the project direction overall, use [org-level Feature Requests](https://github.com/orgs/utPLSQL/discussions/categories/feature-requests) instead. + + A maintainer will mark this discussion as answered once the proposal is accepted or declined. + If accepted, a maintainer will convert it to an issue for implementation tracking -- you do not need to open an issue yourself. + + - type: textarea + id: problem + attributes: + label: Problem or motivation + description: What problem does this feature solve? What is the current limitation or pain point? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: Describe the feature you have in mind. Include examples of how it would be used, ideally with PL/SQL annotation or API examples. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives or workarounds considered + description: Have you found any alternative approaches or partial workarounds? + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Framework area + options: + - Annotations + - Matchers + - Reporters + - Suite / test control + - Coverage + - Transaction control + - Installation / upgrade + - Performance + - Other + validations: + required: true + + - type: input + id: utplsql-version + attributes: + label: utPLSQL version you are using + placeholder: "e.g. 3.1.13" + validations: + required: false diff --git a/.github/DISCUSSION_TEMPLATE/q-a.yml b/.github/DISCUSSION_TEMPLATE/q-a.yml new file mode 100644 index 000000000..30b372760 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/q-a.yml @@ -0,0 +1,61 @@ +title: "[Q&A] " +labels: ["question"] +body: + - type: markdown + attributes: + value: | + ## Question & Answer + Ask anything about **using** utPLSQL — installation, writing tests, CI integration, + reporters, annotations, code coverage, etc. + + **Tips for a fast answer:** + - Include your utPLSQL version and Oracle version. + - Paste a minimal reproducer (test package + expected vs actual output). + - Check the [documentation](https://utplsql.github.io/utPLSQL/) and + [closed Q&A discussions](../../discussions/categories/q-a?discussions_q=is%3Aclosed) first. + + > If this turns out to be a **bug**, a maintainer will convert this discussion into an Issue. + + - type: input + id: utplsql-version + attributes: + label: utPLSQL version + placeholder: "e.g. 3.1.13" + validations: + required: true + + - type: input + id: oracle-version + attributes: + label: Oracle Database version + placeholder: "e.g. 19c, 21c" + validations: + required: true + + - type: textarea + id: question + attributes: + label: Question + description: Describe what you are trying to do and what you have already tried. + validations: + required: true + + - type: textarea + id: reproducer + attributes: + label: Minimal reproducer + description: | + Paste a minimal test package and/or the relevant ut_runner call. + Include the full error message or unexpected output if applicable. + render: sql + validations: + required: false + + - type: textarea + id: output + attributes: + label: Actual output or error + description: Paste any error messages or unexpected output here. + render: text + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 681bc7f3a..f0335781e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -23,4 +23,4 @@ Add any other context or screenshots about the feature request here. > Please do not create issues for generic SQL or PL/SQL questions. There are other forums and communities to help you with those. See [ASKTom](https://asktom.oracle.com) for example. **Want to discuss** -If you want to discuss your issue, join [our SLACK chat](https://join.slack.com/t/utplsql/shared_invite/zt-xwm68udy-4cF_3PNEyczYEbWr38W5ww). +If you want to discuss a problem, please go to [utPLSQL discussions](https://github.com/utPLSQL/utPLSQL/discussions) diff --git a/.github/scripts/create_test_users.sh b/.github/scripts/create_test_users.sh index fe43b7467..6c3030206 100755 --- a/.github/scripts/create_test_users.sh +++ b/.github/scripts/create_test_users.sh @@ -18,6 +18,9 @@ PROMPT Creating UT3_TESTER - Power-user for testing internal framework code create user UT3_TESTER identified by "ut3" default tablespace $UT3_TABLESPACE quota unlimited on $UT3_TABLESPACE; grant create session, create procedure, create type, create table to UT3_TESTER; +# Editioning is enable on test users to validate that utPLSQL works well with editioned schema +alter user UT3_TESTER enable editions; + grant execute on dbms_lock to UT3_TESTER; PROMPT Granting $UT3_DEVELOP_SCHEMA code to UT3_TESTER @@ -52,6 +55,9 @@ PROMPT Creating UT3_USER - minimal privileges user for API testing create user UT3_USER identified by "ut3" default tablespace $UT3_TABLESPACE quota unlimited on $UT3_TABLESPACE; grant create session, create procedure, create type, create table to UT3_USER; +# Editioning is enable on test users to validate that utPLSQL works well with editioned schema +alter user UT3_USER enable editions; + PROMPT Grants for starting a debugging session from UT3_USER grant debug connect session to UT3_USER; grant debug any procedure to UT3_USER; @@ -86,7 +92,7 @@ PROMPT Grants for testing coverage outside of main $UT3_DEVELOP_SCHEMA schema. grant create any procedure, drop any procedure, execute any procedure, create any type, drop any type, execute any type, under any type, select any table, update any table, insert any table, delete any table, create any table, drop any table, alter any table, select any dictionary, create any synonym, drop any synonym, - grant any object privilege, grant any privilege, create public synonym, drop public synonym, create any trigger + grant any object privilege, grant any privilege, create public synonym, drop public synonym, create any trigger, drop any trigger to UT3_TESTER_HELPER; grant create job to UT3_TESTER_HELPER; 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/install.sh b/.github/scripts/install.sh index 46750b89c..5f034951c 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -19,3 +19,13 @@ set verify off alter session set plsql_optimize_level=0; @${INSTALL_FILE} $UT3_DEVELOP_SCHEMA $UT3_DEVELOP_SCHEMA_PASSWORD SQL + +#Enable editioning on the UT3_DEVELOP schema +time "$SQLCLI" sys/$ORACLE_PWD@//$CONNECTION_STR AS SYSDBA <<-SQL +whenever sqlerror exit failure rollback +set feedback off +set verify off + +alter user $UT3_DEVELOP_SCHEMA enable editions; +exit +SQL diff --git a/.github/scripts/post_release_announcement.sh b/.github/scripts/post_release_announcement.sh new file mode 100755 index 000000000..c74394505 --- /dev/null +++ b/.github/scripts/post_release_announcement.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Posts a release announcement to the utPLSQL org .github repository's +# Announcements discussion category. +# +# Required environment variables: +# GH_TOKEN - GitHub token with write:discussion scope (ORG_DISCUSSION_TOKEN) +# RELEASE_TAG - e.g. v3.2.3 +# RELEASE_BODY - markdown release notes +# RELEASE_URL - URL of the GitHub release page + +set -euo pipefail + +ORG="utPLSQL" +REPO_NAME=".github" + +# Get org node ID +ORG_DATA=$(gh api graphql -f query='query($org: String!) { + organization(login: $org) { + id + } +}' -f org="$ORG") +ORG_ID=$(echo "$ORG_DATA" | jq -r '.data.organization.id') + +# Get repository node ID +REPO_DATA=$(gh api graphql -f query='query($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { + id + } +}' -f owner="$ORG" -f name="$REPO_NAME") +REPO_ID=$(echo "$REPO_DATA" | jq -r '.data.repository.id') + +# Get Announcements category ID +CATEGORY_DATA=$(gh api graphql -f query='query($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { + discussionCategories(first: 20) { + nodes { id name } + } + } +}' -f owner="$ORG" -f name="$REPO_NAME") +CATEGORY_ID=$(echo "$CATEGORY_DATA" | jq -r \ + '.data.repository.discussionCategories.nodes[] | select(.name == "Announcements") | .id') + +BODY="${RELEASE_BODY} + +
This discussion was created from the release ${RELEASE_TAG}." + +DISCUSSION_URL=$(gh api graphql -f query='mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) { + createDiscussion(input: { + repositoryId: $repoId, + categoryId: $categoryId, + title: $title, + body: $body + }) { + discussion { url } + } +}' \ + -f repoId="$REPO_ID" \ + -f categoryId="$CATEGORY_ID" \ + -f title="utPLSQL ${RELEASE_TAG} released" \ + -f body="$BODY" \ + --jq '.data.createDiscussion.discussion.url') + +echo "Announcement posted: $DISCUSSION_URL" diff --git a/.github/scripts/publish_release_post.sh b/.github/scripts/publish_release_post.sh new file mode 100755 index 000000000..c9bade4f4 --- /dev/null +++ b/.github/scripts/publish_release_post.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Publishes a release announcement post to utplsql.github.io. +# Creates the post file, prepends an entry to docs/index.md, +# and inserts the post into the mkdocs.yml nav. +# +# Required environment variables: +# API_TOKEN_GITHUB - GitHub token with write access to utPLSQL.github.io +# RELEASE_TAG - e.g. v3.2.01 +# RELEASE_BODY - markdown release notes (from github.event.release.body) +# RELEASE_DATE - ISO-8601 publish timestamp (from github.event.release.published_at) + +set -euo pipefail + +GITHUB_IO_REPO="utPLSQL/utPLSQL.github.io" +GITHUB_IO_BRANCH="main" + +VERSION="${RELEASE_TAG#v}" +POST_DATE=$(echo "${RELEASE_DATE}" | cut -c1-10) +POST_FILENAME="${POST_DATE}-version${VERSION}-released.md" +POST_TITLE="utPLSQL ${RELEASE_TAG} released" +POST_NAV_TITLE="utPLSQL ${VERSION} released" +POST_PATH='docs/announcements/posts' + +mkdir -p github_io +cd github_io +git clone --depth 1 "https://${API_TOKEN_GITHUB}@github.com/${GITHUB_IO_REPO}" -b "${GITHUB_IO_BRANCH}" . + +# Format the release body for the post: +# 1. Convert bare GitHub issue/PR URLs to [#NUMBER](URL) +# e.g. https://github.com/utPLSQL/utPLSQL/pull/1320 +# -> [#1320](https://github.com/utPLSQL/utPLSQL/pull/1320) +# 2. Convert the auto-generated "Full Changelog" bare URL to a labelled link +# e.g. **Full Changelog**: https://.../compare/v3.2.01...v3.2.2 +# -> **Full Changelog**: [v3.2.01...v3.2.2](https://.../compare/v3.2.01...v3.2.2) +FORMATTED_BODY=$(echo "${RELEASE_BODY}" \ + | sed -E 's@https://github\.com/([^/]+)/([^/]+)/(pull|issues)/([0-9]+)@[#\4](https://github.com/\1/\2/\3/\4)@g' \ + | sed -E 's@\*\*Full Changelog\*\*: (https://[^ ]+/compare/([^ ]+))@\*\*Full Changelog\*\*: [\2](\1)@g') + +# Create the post file +cat > "${POST_PATH}/${POST_FILENAME}" <> $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_changelog.sh b/.github/scripts/update_changelog.sh new file mode 100755 index 000000000..011c08819 --- /dev/null +++ b/.github/scripts/update_changelog.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Updates CHANGELOG.md by prepending a new section for the release. +# +# Required environment variables: +# RELEASE_TAG - e.g. v3.2.3 +# RELEASE_BODY - markdown release notes +# RELEASE_DATE - ISO-8601 date string (only the first 10 chars are used) +# TARGET_BRANCH - branch to push the updated changelog to + +set -euo pipefail + +python3 - <<'PYEOF' +import os + +tag = os.environ['RELEASE_TAG'] +body = os.environ['RELEASE_BODY'].strip() +date = os.environ['RELEASE_DATE'][:10] + +new_section = f"## [{tag}] - {date}\n\n{body}\n\n---\n\n" + +with open('CHANGELOG.md', 'r') as f: + content = f.read() + +marker = '\n---\n' +pos = content.index(marker) + len(marker) +updated = content[:pos] + '\n' + new_section + content[pos:] + +with open('CHANGELOG.md', 'w') as f: + f.write(updated) + +print(f"Added {tag} to CHANGELOG.md") +PYEOF + +git add CHANGELOG.md +git commit -m "Add release notes for ${RELEASE_TAG} to CHANGELOG.md [skip ci]" +git push origin HEAD:"${TARGET_BRANCH}" 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..3ca9f34d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,9 @@ on: branches-ignore: [ main ] pull_request: branches: [ develop ] + schedule: + - cron: '0 8 * * 5' # run scheduled at 8:00 AM every Friday + timezone: "Europe/Nicosia" workflow_dispatch: @@ -17,6 +20,7 @@ jobs: build: name: Build and test on ${{matrix.db_version_name}} DB runs-on: ubuntu-latest + timeout-minutes: 90 env: ORACLE_VERSION: ${{matrix.oracle-version}} ORACLE_SID: ${{matrix.oracle-sid}} @@ -28,37 +32,21 @@ jobs: matrix: include: - id: 1 - db_version_name: '11XE' - oracle-sid: 'XE' - oracle-version: "gvenzl/oracle-xe:11-full" - oracle-base: '/u01/app/oracle' - - id: 2 - db_version_name: '12.1EE' + db_version_name: '19se' oracle-sid: 'ORCLCDB' - oracle-version: "utplsqlv3/oracledb:12c-r1-ee-small" - oracle-base: '/opt/oracle' -# - id: 3 -# db_version_name: '12.2se' -# oracle-sid: 'ORCLCDB' -# oracle-version: "utplsqlv3/oracledb:12c-r2-se2-small" -# oracle-base: '/opt/oracle' - - id: 4 - db_version_name: '18XE' - oracle-sid: 'XE' - oracle-version: "gvenzl/oracle-xe:18-slim" - oracle-base: '/opt/oracle' -# TODO - need to add healthcheck.sh into our containers -# - id: 5 -# db_version_name: '19se' -# oracle-sid: 'ORCLCDB' -# oracle-version: "utplsqlv3/oracledb:19c-se2-small" -# oracle-base: '/opt/oracle' - - id: 6 + oracle-version: "utplsqlv3/oracledb:19c-se2-small" + check-coding-style: true + validate-utplsql-uninstall: true + - id: 2 db_version_name: '21XE' oracle-sid: 'XE' - oracle-version: "gvenzl/oracle-xe:21-slim" - oracle-base: '/opt/oracle' - + oracle-version: "gvenzl/oracle-xe:21-slim-faststart" + - id: 3 + db_version_name: '23-free' + oracle-sid: 'FREEPDB1' + oracle-version: "gvenzl/oracle-free:23-slim-faststart" + fips-enabled: true + run-sonar-scanner: true services: html_checker: image: ghcr.io/validator/validator:latest @@ -83,13 +71,13 @@ 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 + - uses: c-py/action-dotenv-to-setenv@v5 with: env-file: .github/variables/.env - - uses: FranzDiebold/github-env-vars-action@v2 #https://github.com/marketplace/actions/github-environment-variables-action + - uses: FranzDiebold/github-env-vars-action@v2.8.0 #https://github.com/marketplace/actions/github-environment-variables-action - name: Set build version number env variables id: set-build-version-number-vars @@ -103,55 +91,84 @@ jobs: # For PR build - test using target branch as framework, for branch build use self as testing framework run: git clone --depth=1 --branch=${CI_BASE_REF:-$CI_REF_NAME} https://github.com/utPLSQL/utPLSQL.git $UTPLSQL_DIR + - name: Enable FIPS 140 on Oracle DB + id: enable-fips + if: ${{ matrix.fips-enabled == true }} + run: | + docker exec oracle bash -c " + echo \"ALTER SYSTEM SET DBFIPS_140=TRUE SCOPE=SPFILE; + SHUTDOWN IMMEDIATE; + STARTUP; + PROMPT Should show true for DBFIPS_140 + SELECT name, value FROM v$parameter WHERE name = 'DBFIPS_140'; + EXIT;\" | sqlplus -s / as sysdba + " + docker exec oracle bash -c "until healthcheck.sh; do echo 'Waiting for DB restart...'; sleep 1; done" - name: Update privileges on sources run: chmod -R go+w ./{source,test,examples,${UTPLSQL_DIR}/source} + - name: Cache OJDBC jars + id: cache-ojdbc + uses: actions/cache@v4 + with: + path: ${{ env.OJDBC_HOME }} + key: ${{ runner.os }}-ojdbc-${{ env.OJDBC_URL }}-v1 + - name: Add OJDBC home + if: steps.cache-ojdbc.outputs.cache-hit != 'true' id: get-ojdbc run: mkdir -p ${OJDBC_HOME} && curl -Lk -o ${OJDBC_HOME}/ojdbc8.jar ${OJDBC_URL}/ojdbc8.jar && curl -Lk -o ${OJDBC_HOME}/orai18n.jar ${OJDBC_URL}/orai18n.jar + - name: Cache utPLSQL-cli + id: cache-utplsql-cli + uses: actions/cache@v4 + with: + path: utPLSQL-cli + key: ${{ runner.os }}-utplsql-cli-v3.1.8 + - name: Install utPLSQL-cli + if: steps.cache-utplsql-cli.outputs.cache-hit != 'true' id: install-utplsql-cli - run: curl -Lk -o utPLSQL-cli.zip "https://github.com/utPLSQL/utPLSQL-cli/releases/download/v3.1.8/utPLSQL-cli.zip" && unzip utPLSQL-cli.zip && chmod -R u+x utPLSQL-cli + run: curl -Lk -o utPLSQL-cli.zip "https://github.com/utPLSQL/utPLSQL-cli/releases/download/3.1.9/utPLSQL-cli.zip" && unzip utPLSQL-cli.zip && chmod -R u+x utPLSQL-cli - name: Install utPLSQL id: install-utplsql - run: docker run --rm -v $(pwd):/utPLSQL -w /utPLSQL --network host --entrypoint bash ${DOCKER_ENV} ${ORACLE_VERSION} .github/scripts/install.sh + run: docker exec -w /utPLSQL ${DOCKER_ENV} oracle .github/scripts/install.sh - name: Check code style - if: ${{ matrix.id == 1 }} + if: ${{ matrix.check-style == true }} id: check-coding-style - run: docker run --rm -v $(pwd):/utPLSQL -w /utPLSQL --network host --entrypoint "$SQLCLI" ${DOCKER_ENV} ${ORACLE_VERSION} $UT3_DEVELOP_SCHEMA/$UT3_DEVELOP_SCHEMA_PASSWORD@//$CONNECTION_STR @development/utplsql_style_check.sql + run: docker exec -w /utPLSQL ${DOCKER_ENV} oracle "$SQLCLI" ${UT3_DEVELOP_SCHEMA}/${UT3_DEVELOP_SCHEMA_PASSWORD}@//${CONNECTION_STR} @/utPLSQL/development/utplsql_style_check.sql - name: Validate utPLSQL uninstall - if: ${{ matrix.id == 1 }} + if: ${{ matrix.validate-utplsql-uninstall == true }} id: validate-uninstall - run: docker run --rm -v $(pwd):/utPLSQL -w /utPLSQL --network host --entrypoint bash ${DOCKER_ENV} ${ORACLE_VERSION} .github/scripts/uninstall_validate_utplsql.sh + run: docker exec -w /utPLSQL ${DOCKER_ENV} oracle .github/scripts/uninstall_validate_utplsql.sh - name: Reinstall utPLSQL - if: ${{ matrix.id == 1 }} + if: ${{ matrix.validate-utplsql-uninstall == true }} id: reinstall-utplsql - run: docker run --rm -v $(pwd):/utPLSQL -w /utPLSQL --network host --entrypoint bash ${DOCKER_ENV} ${ORACLE_VERSION} .github/scripts/install.sh + run: docker exec -w /utPLSQL ${DOCKER_ENV} oracle .github/scripts/install.sh - name: Create test users id: create-test-users - run: docker run --rm -v $(pwd):/utPLSQL -w /utPLSQL --network host --entrypoint bash ${DOCKER_ENV} ${ORACLE_VERSION} .github/scripts/create_test_users.sh + run: docker exec -w /utPLSQL ${DOCKER_ENV} oracle .github/scripts/create_test_users.sh - name: Install utPLSQL release id: install-utplsql-release - run: docker run --rm -v $(pwd):/utPLSQL -w /utPLSQL --network host --entrypoint bash ${DOCKER_ENV} ${ORACLE_VERSION} .github/scripts/install_utplsql_release.sh + run: docker exec -w /utPLSQL ${DOCKER_ENV} oracle .github/scripts/install_utplsql_release.sh - name: Run Examples id: run-examples - run: docker run --rm -v $(pwd):/utPLSQL -w /utPLSQL --network host --entrypoint bash ${DOCKER_ENV} ${ORACLE_VERSION} .github/scripts/run_examples.sh + run: docker exec -w /utPLSQL ${DOCKER_ENV} oracle .github/scripts/run_examples.sh - name: Install tests id: install-tests - run: docker run --rm -v $(pwd):/utPLSQL -w /utPLSQL --network host --entrypoint bash ${DOCKER_ENV} ${ORACLE_VERSION} test/install_tests.sh + run: docker exec -w /utPLSQL ${DOCKER_ENV} oracle test/install_tests.sh - name: Run Tests id: run-tests - run: bash test/run_tests.sh + run: test/run_tests.sh #Start Needed to diagnose occasional failures of DB on test runs - name: Prepare diagnostic directory @@ -167,7 +184,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 +195,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 @@ -186,22 +203,22 @@ jobs: fail_ci_if_error: true # optional (default = false) - name: Publish unit test results - uses: EnricoMi/publish-unit-test-result-action@v1.24 + uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: files: junit_test_results.xml - name: SonarCloud Scan id: sonar - if: ${{ always() && matrix.db_version_name == '21XE' }} - uses: SonarSource/sonarcloud-github-action@master + if: ${{ always() && matrix.run-sonar-scanner == true }} + uses: SonarSource/sonarqube-scan-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: args: > -Dsonar.buildString=${{ format( '{0}.{1}', env.UTPLSQL_BUILD_VERSION, matrix.id ) }} - -Dsonar.plsql.jdbc.url=${{ format( 'jdbc:oracle:thin:@//oracle:1521/{0}', env.ORACLE_SID ) }} + -Dsonar.plsql.jdbc.url=${{ format( 'jdbc:oracle:thin:@//localhost:1521/{0}', env.ORACLE_SID ) }} -Dsonar.plsql.jdbc.driver.path=${{ format( '{0}/ojdbc8.jar', env.OJDBC_HOME ) }} publish: @@ -209,26 +226,26 @@ jobs: needs: [ build ] concurrency: publish runs-on: ubuntu-latest + timeout-minutes: 30 env: 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 == '' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: echo "API_TOKEN_GITHUB=${GITHUB_TOKEN}" >> $GITHUB_ENV - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ env.API_TOKEN_GITHUB }} - - uses: c-py/action-dotenv-to-setenv@v2 + - uses: c-py/action-dotenv-to-setenv@v5 with: env-file: .github/variables/.env - - uses: FranzDiebold/github-env-vars-action@v2 #https://github.com/marketplace/actions/github-environment-variables-action + - uses: FranzDiebold/github-env-vars-action@v2.8.0 #https://github.com/marketplace/actions/github-environment-variables-action - name: Set buid version number env variables id: set-build-version-number-vars @@ -241,21 +258,30 @@ 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 + pip install mkdocs-git-committers-plugin-2 + mike deploy -p develop dispatch: name: Dispatch downstream builds concurrency: trigger needs: [ build, publish ] runs-on: ubuntu-latest + timeout-minutes: 15 if: | github.repository == 'utPLSQL/utPLSQL' && github.base_ref == null && ( startsWith( github.ref, 'refs/heads/release/v' ) || github.ref == 'refs/heads/develop' ) @@ -265,22 +291,9 @@ jobs: # repo: [ 'utPLSQL/utPLSQL-v2-v3-migration' ] steps: - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v4 with: token: ${{ secrets.API_TOKEN_GITHUB }} repository: ${{ matrix.repo }} event-type: utPLSQL-build - slack-workflow-status: - if: always() - name: Post Workflow Status To Slack - needs: [ build, publish, dispatch ] - runs-on: ubuntu-latest - steps: - - name: Slack Workflow Notification - uses: Gamesight/slack-workflow-status@master - with: - repo_token: ${{secrets.GITHUB_TOKEN}} - slack_webhook_url: ${{secrets.SLACK_WEBHOOK_URL}} - name: 'Github Actions[bot]' - icon_url: 'https://octodex.github.com/images/mona-the-rivetertocat.png' diff --git a/.github/workflows/rebuild_docs.yml b/.github/workflows/rebuild_docs.yml new file mode 100644 index 000000000..b9f3e4c45 --- /dev/null +++ b/.github/workflows/rebuild_docs.yml @@ -0,0 +1,56 @@ +name: Rebuild older docs versions +on: + workflow_dispatch: + inputs: + versions: + description: 'Space-separated list of versions to rebuild (e.g. "v3.1.14 v3.1.13")' + required: true + default: 'v3.2.2 v3.2.01 v3.1.14 v3.1.13 v3.1.12 v3.1.11 v3.1.10 v3.1.9 v3.1.8 v3.1.6 v3.1.5 v3.1.4 v3.1.3 v3.1.2 v3.1.1 v3.1.0 v3.0.4 v3.0.3 v3.0.2 v3.0.1 v3.0.0 v3.0.0-beta' + +defaults: + run: + shell: bash + +jobs: + rebuild: + name: Rebuild documentation for older versions + concurrency: publish + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + + steps: + - name: 🔍 API_TOKEN_GITHUB + if: env.API_TOKEN_GITHUB == '' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: echo "API_TOKEN_GITHUB=${GITHUB_TOKEN}" >> $GITHUB_ENV + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ env.API_TOKEN_GITHUB }} + + - uses: c-py/action-dotenv-to-setenv@v5 + with: + env-file: .github/variables/.env + + - uses: FranzDiebold/github-env-vars-action@v2.8.0 + + - name: Install mkdocs stack + 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 + pip install mkdocs-git-committers-plugin-2 + + - name: Rebuild specified versions + run: | + for VERSION in ${{ inputs.versions }}; do + echo "=== Rebuilding $VERSION ===" + git checkout "tags/${VERSION}" -- docs/ + mike deploy -p "${VERSION}" + git checkout HEAD -- docs/ mkdocs.yml + done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd977d447..7df03680d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,65 +10,47 @@ 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 + timeout-minutes: 60 + env: + API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + RELEASE_BODY: ${{ github.event.release.body }} + RELEASE_DATE: ${{ github.event.release.published_at }} + RELEASE_URL: ${{ github.event.release.html_url }} + TARGET_BRANCH: ${{ github.event.release.target_commitish }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: c-py/action-dotenv-to-setenv@v2 + token: ${{ env.API_TOKEN_GITHUB }} + - uses: c-py/action-dotenv-to-setenv@v5 with: env-file: .github/variables/.env - - uses: FranzDiebold/github-env-vars-action@v2 #https://github.com/marketplace/actions/github-environment-variables-action + - uses: FranzDiebold/github-env-vars-action@v2.8.0 #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: Update CHANGELOG.md with release notes + run: .github/scripts/update_changelog.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 - - 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 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 + pip install mkdocs-git-committers-plugin-2 + mike deploy -p -u ${UTPLSQL_VERSION} latest + mkdocs build --clean -f mkdocs_offline.yml rm -rf docs/* cp -r -v site/* docs git add . @@ -78,29 +60,23 @@ jobs: run: | git archive --prefix=utPLSQL/ -o utPLSQL.zip --format=zip HEAD git archive --prefix=utPLSQL/ -o utPLSQL.tar.gz --format=tar.gz HEAD - md5sum utPLSQL.zip --tag > utPLSQL.zip.md5 - md5sum utPLSQL.tar.gz --tag > utPLSQL.tar.gz.md5 + sha256sum utPLSQL.zip --tag > utPLSQL.zip.sha256 + sha256sum utPLSQL.tar.gz --tag > utPLSQL.tar.gz.sha256 - name: Release uses: softprops/action-gh-release@v1 with: files: | utPLSQL.zip - utPLSQL.zip.md5 + utPLSQL.zip.sha256 utPLSQL.tar.gz - utPLSQL.tar.gz.md5 + utPLSQL.tar.gz.sha256 - slack-workflow-status: - if: always() - name: Post Workflow Status To Slack - needs: [ publish, upload_artifacts ] - runs-on: ubuntu-latest - steps: - - name: Slack Workflow Notification - uses: Gamesight/slack-workflow-status@master - with: - repo_token: ${{secrets.GITHUB_TOKEN}} - slack_webhook_url: ${{secrets.SLACK_WEBHOOK_URL}} - name: 'Github Actions[bot]' - icon_url: 'https://octodex.github.com/images/mona-the-rivetertocat.png' + - name: Post release announcement to org discussions + env: + GH_TOKEN: ${{ secrets.API_TOKEN_GITHUB }} + run: .github/scripts/post_release_announcement.sh + + - name: Publish release post to utplsql.github.io + run: .github/scripts/publish_release_post.sh diff --git a/.github/workflows/test_publishing.yml b/.github/workflows/test_publishing.yml new file mode 100644 index 000000000..6f079ac54 --- /dev/null +++ b/.github/workflows/test_publishing.yml @@ -0,0 +1,55 @@ +name: Test publishing +on: + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + + upload_artifacts: + name: Upload archives + concurrency: upload + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + RELEASE_TAG: "v3.2.3" + RELEASE_BODY: | + ## What's Changed + * Fix issue with `UT_TAP_REPORTER` on Oracle 23.26 by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1359 + * Fixed support for camelCase in context `--%name` annotation. by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1361 + * utPLSQL can be installed in editioned schema. by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1362 + * Removed coveralls reporter by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1363 + * Clarify behavior of before/after procedures in suitepath by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1364 + * `throws` can reference exception without schema or package name by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1365 + * `throws` works with uninitialized exception variables by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1366 + + + **Full Changelog**: https://github.com/utPLSQL/utPLSQL/compare/v3.2.2...v.3.2.3 + RELEASE_DATE: "2026-07-10 08:30:22" + RELEASE_URL: "https://github.com/utPLSQL/utPLSQL/releases/tag/v3.2.3" + TARGET_BRANCH: "develop" + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ env.API_TOKEN_GITHUB }} + - uses: c-py/action-dotenv-to-setenv@v5 + with: + env-file: .github/variables/.env + - uses: FranzDiebold/github-env-vars-action@v2.8.0 #https://github.com/marketplace/actions/github-environment-variables-action + +# - name: Update CHANGELOG.md with release notes +# run: .github/scripts/update_changelog.sh + +# - name: Post release announcement to org discussions +# env: +# GH_TOKEN: ${{ secrets.API_TOKEN_GITHUB }} +# run: .github/scripts/post_release_announcement.sh + + - name: Publish release post to utplsql.github.io + run: .github/scripts/publish_release_post.sh + diff --git a/BUILD_NO b/BUILD_NO new file mode 100644 index 000000000..697eb0993 --- /dev/null +++ b/BUILD_NO @@ -0,0 +1 @@ +4532 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..faf1f504a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,630 @@ +# Changelog + +All notable changes to utPLSQL are documented here. +Releases are available on the [GitHub Releases page](https://github.com/utPLSQL/utPLSQL/releases). + +--- + +## [v.3.2.3] - 2026-07-10 + +## What's Changed +* Fix issue with `UT_TAP_REPORTER` on Oracle 23.26 by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1359 +* Fixed support for camelCase in context `--%name` annotation. by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1361 +* utPLSQL can be installed in editioned schema. by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1362 +* Removed coveralls reporter by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1363 +* Clarify behavior of before/after procedures in suitepath by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1364 +* `throws` can reference exception without schema or package name by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1365 +* `throws` works with uninitialized exception variables by @jgebal in https://github.com/utPLSQL/utPLSQL/pull/1366 + + +**Full Changelog**: https://github.com/utPLSQL/utPLSQL/compare/v3.2.2...v.3.2.3 + +--- + + +## [v3.2.2] - 2026-05-24 + +## What's Changed +* Filtering by tags fixed for schemas containing suite-paths and contexts by @jgebal in [1325](https://github.com/utPLSQL/utPLSQL/pull/1325) +* Fixed issue with reporter failing to initialize by @jgebal in [1354](https://github.com/utPLSQL/utPLSQL/pull/1354) +* Fixed issue with duplicate sources from dba_source by @jgebal in [1355](https://github.com/utPLSQL/utPLSQL/pull/1355) +* Allow HTML coverage on FIPS-enabled DB by @jgebal in [1356](https://github.com/utPLSQL/utPLSQL/pull/1356) + + +**Full Changelog**: https://github.com/utPLSQL/utPLSQL/compare/v3.2.01...v3.2.2 + +--- + +## [v3.2.01] - 2026-04-22 + +## Important + - Dropped support for Oracle Database versions older than 19c + +## New features + - Add TAP reporter by @WayneNani in [#1305](https://github.com/utPLSQL/utPLSQL/pull/1305) + +## Enhancements and bug fixes + - Added duplicate detection on cursor comparison using `join_by` by @lwasylow in [#1295](https://github.com/utPLSQL/utPLSQL/pull/1295) + - Fix code causing the object duration Oracle exception on oracle 23.26 by @lwasylow in [#1310](https://github.com/utPLSQL/utPLSQL/pull/1310) + - Packages with annotation-like comments are not test suites by @jgebal in [#1313](https://github.com/utPLSQL/utPLSQL/pull/1313) + - Performance improvements to annotation parsing by @lwasylow in [#1312](https://github.com/utPLSQL/utPLSQL/pull/1319) + - Updated documentation and copyright banners across source files @jgebal in [#1319](https://github.com/utPLSQL/utPLSQL/pull/1319) + +--- + +## [v3.1.14] - 2024-02-19 + +## New features + + - Added support for `and` and `or` operators when running tests by tags by @lwasylow in [#1250](https://github.com/utPLSQL/utPLSQL/pull/1250) + +## Enhancements + + - Allow for test runs over 4 hours by @jgebal in [#1243](https://github.com/utPLSQL/utPLSQL/pull/1243) + - Framework performance improvements by @jgebal in [#1249](https://github.com/utPLSQL/utPLSQL/pull/1249) + - Support multiple expectation failures with teamcity reporter by @jgebal in [#1251](https://github.com/utPLSQL/utPLSQL/pull/1251) + - Update TFS reporter to correct format by @lwasylow in [#1270](https://github.com/utPLSQL/utPLSQL/pull/1270) + +## Bug fixes + + - Address issue where the `not_to(contain)` did not run correctly by @lwasylow in [#1246](https://github.com/utPLSQL/utPLSQL/pull/1246) + - Fix regex to be NLS_SORT independent by @jgebal in [#1253](https://github.com/utPLSQL/utPLSQL/pull/1253) + - Fix output length error and output buffer. by @jgebal in [#1255](https://github.com/utPLSQL/utPLSQL/pull/1255) + - (docs): Update running-unit-tests.md - remove example by @gassenmj in [#1261](https://github.com/utPLSQL/utPLSQL/pull/1261) + - The line-rate is not recorded for packages and classes in cobertura coverage reporter by @lwasylow in [#1269](https://github.com/utPLSQL/utPLSQL/pull/1269) + - (docs): Fix nested list issue by @iamrachid in [#1274](https://github.com/utPLSQL/utPLSQL/pull/1274) + +--- + +## [v3.1.13] - 2022-12-11 + +## New features + + - Add ability to run tests by part of a name. Fixed in [#1203](https://github.com/utPLSQL/utPLSQL/pull/1203), resolves [#470](https://github.com/utPLSQL/utPLSQL/issues/470) + +## Enhancements + + - Added documentation section on creating a custom reporter. Fixed in [#1225](https://github.com/utPLSQL/utPLSQL/pull/1225), resolves [#710](https://github.com/utPLSQL/utPLSQL/issues/701) + - Add ability to specify code coverage objects include/exclude masks as regular expressions. Fixed in [#1186](https://github.com/utPLSQL/utPLSQL/issues/1186), resolves [#1053](https://github.com/utPLSQL/utPLSQL/issues/1053) + +## Bug fixes + + - Comparing collection that have long type names. Fixed in [#1238](https://github.com/utPLSQL/utPLSQL/issues/1238), resolves [#1235](https://github.com/utPLSQL/utPLSQL/issues/1235). + - Code coverage reporting on code with long lines. Fixed in [#1240](https://github.com/utPLSQL/utPLSQL/issues/1240), resolves [#1232](https://github.com/utPLSQL/utPLSQL/issues/1232), [#1087](https://github.com/utPLSQL/utPLSQL/issues/1087). + - Code coverage reporting does not exclude tests. Fixed in [#1226](https://github.com/utPLSQL/utPLSQL/issues/1226), resolves [#1222](https://github.com/utPLSQL/utPLSQL/issues/1222). + - Uninstall script buffer overflow. Fixed in [#1221](https://github.com/utPLSQL/utPLSQL/issues/1221), resolves [#1220](https://github.com/utPLSQL/utPLSQL/issues/1220). + +## Internal improvements + + - Improved process of generating utPLSQL documentation. Implemented in [689bbd0](https://github.com/utPLSQL/utPLSQL/commit/689bbd0e365ed919315c29727bc10fbfc0dadce8), resolves [#1237](https://github.com/utPLSQL/utPLSQL/issues/1237). + - Removed username env variables for internal development. Implemented in [#1201](https://github.com/utPLSQL/utPLSQL/issues/1221), resolves [#1200](https://github.com/utPLSQL/utPLSQL/issues/1200). + - Test execution data of utPLSQL project not showing on SonarCloud. Implemented in [#1199](https://github.com/utPLSQL/utPLSQL/issues/1199), resolves [#1198](https://github.com/utPLSQL/utPLSQL/issues/1198). + +--- + +## [v3.1.12] - 2022-02-25 + +## New features + - Added support for description in the `--%disabled` annotation. See [documentation](https://github.com/utPLSQL/utPLSQL/blob/4515e0b5a3b4b0de0c2198db3235ef8614bff4d8/docs/userguide/annotations.md#disabled). Implemented in: [#1183](https://github.com/utPLSQL/utPLSQL/pull/1183), resolves [#610](https://github.com/utPLSQL/utPLSQL/issues/610). + - Added support for native `JSON` datatype on Oracle `21c`. Implemented in [#1181](https://github.com/utPLSQL/utPLSQL/pull/1181), resolves [#1114](https://github.com/utPLSQL/utPLSQL/issues/1114). + - Added new mather `to_be_within( distance|pct ).of_(expoected)`. See [documentation](https://github.com/utPLSQL/utPLSQL/blob/fd7ef9c14111a48e03fff7851f990c7192539170/docs/userguide/expectations.md#to_be_within-of). Implemented in [#1076](https://github.com/utPLSQL/utPLSQL/pull/1076), resolves [#77](https://github.com/utPLSQL/utPLSQL/issues/77). + +## Enhancements + +- Improved performance of SQL used to retrieve Coverage sources. Implemented in [#1187](https://github.com/utPLSQL/utPLSQL/pull/1187), resolves [#1169](https://github.com/utPLSQL/utPLSQL/issues/1169). +- Added ability for utPLSQL to gather coverage on code invoking DBMS_STATS package. Implemented in [#1184](https://github.com/utPLSQL/utPLSQL/pull/1184), resolves [#1097](https://github.com/utPLSQL/utPLSQL/issues/1097), [#1094](https://github.com/utPLSQL/utPLSQL/issues/1094). +- Fixed typos and improved documentation. Implemented in [#1173](https://github.com/utPLSQL/utPLSQL/pull/1173), [#1171](https://github.com/utPLSQL/utPLSQL/pull/1171) + +## Bug fixes + +- Actual and Expected are now correctly reported when comparing `JSON` data. Implemented in [#1181](https://github.com/utPLSQL/utPLSQL/pull/1181), resolves [#1113](https://github.com/utPLSQL/utPLSQL/issues/1113). +- Packages with removed annotations are now correctly recognized as non-utPLSQL packages. Implemented in [#1180](https://github.com/utPLSQL/utPLSQL/pull/1180), resolves [#1177](https://github.com/utPLSQL/utPLSQL/issues/1177). +- Fixed issues with comparison of nested object structures. Implemented in [#1179](https://github.com/utPLSQL/utPLSQL/pull/1179), resolves [#1082](https://github.com/utPLSQL/utPLSQL/issues/1082), [#1083](https://github.com/utPLSQL/utPLSQL/issues/1083), [#1098](https://github.com/utPLSQL/utPLSQL/issues/1098). + +## Internal improvements + +- Moved build and test process for utPLSQL from Travis to GithubActions. Implemented in [#1175](https://github.com/utPLSQL/utPLSQL/pull/1175) + +--- + +## [v3.1.11] - 2021-11-17 + +## Enhancements + +- utPLSQL can now be used to generate coverage reports for external tools. See [documentation](https://github.com/utPLSQL/utPLSQL/blob/955de5c2dc527a98a6f5dc0dd8bd8d81a44c459f/docs/userguide/coverage.md#reporting-coverage-outside-of-utplsql) + Implemented in: [#1079](https://github.com/utPLSQL/utPLSQL/pull/1079), resolves [#1025](https://github.com/utPLSQL/utPLSQL/issues/1025) +- Enhanced UT_COVERAGE_COBERTURA_REPORTER to better support TFS and GitLab. Implemented in [#1137](https://github.com/utPLSQL/utPLSQL/pull/1137) and [#1140](https://github.com/utPLSQL/utPLSQL/pull/1140), resolves [#1107](https://github.com/utPLSQL/utPLSQL/issues/1107) +- Added support for installation on Oracle 21c - removed dependency on DBMS_OBFUSCATION_TOOLKIT. Implemented in [#1112](https://github.com/utPLSQL/utPLSQL/pull/1112), resolves [#1111](https://github.com/utPLSQL/utPLSQL/issues/1111) and [#1127](https://github.com/utPLSQL/utPLSQL/issues/1127) +- Added support for running utPLSQL framework in parallel-enabled database. Implemented in [#1160](https://github.com/utPLSQL/utPLSQL/pull/1160), resolves [#1134](https://github.com/utPLSQL/utPLSQL/issues/1134) + +## Bug fixes + +- Suite structure is built properly even with other than English TNS settings. Implemented in [#1061](https://github.com/utPLSQL/utPLSQL/pull/1061), resolves [#1060](https://github.com/utPLSQL/utPLSQL/issues/1060) +- Fixed XML content reporting (CDATA) in UT_REALTIME_REPORTER used by SQLDeveloper plugin. Implemented in [#1075](https://github.com/utPLSQL/utPLSQL/pull/1075), resolves [#1073](https://github.com/utPLSQL/utPLSQL/issues/1073) +- Fixed XML content reporting (CDATA) in JUnit reporter - regression. Implemented in [#1085](https://github.com/utPLSQL/utPLSQL/pull/1085), resolves [#1084](https://github.com/utPLSQL/utPLSQL/issues/1084) +- Fixed issue with utPLSQL failing to run coverage reporting when trigger has overlapping name with procedure/function/package/type. Implemented in [#1091](https://github.com/utPLSQL/utPLSQL/pull/1091), resolves [#1086](https://github.com/utPLSQL/utPLSQL/issues/1086) +- Fixed issue with parsing utPLSQL suites with DDL trigger when usign AUTHID clause. Implemented in [#1093](https://github.com/utPLSQL/utPLSQL/pull/1093), resolves [#1088](https://github.com/utPLSQL/utPLSQL/issues/1088) + +## Internal improvements + +- Improved how privilege checks are handled by framework installation. Implemented in [#1056](https://github.com/utPLSQL/utPLSQL/pull/1056), resolves [#1050](https://github.com/utPLSQL/utPLSQL/issues/1050) +- Restructured installation instructions to make it more readable. Implemented in [#1063](https://github.com/utPLSQL/utPLSQL/pull/1063), resolves [#1062](https://github.com/utPLSQL/utPLSQL/issues/1062) +- Updated database requirements in documentation. Implemented in [#1065](https://github.com/utPLSQL/utPLSQL/pull/1065), resolves [#1064](https://github.com/utPLSQL/utPLSQL/issues/1064) +- Removed duplicated call to install profiler tables. Implemented in [#1164](https://github.com/utPLSQL/utPLSQL/pull/1164), resolves [#1149](https://github.com/utPLSQL/utPLSQL/issues/1149) +- Fixed failing internal framework tests on Oracle 21c. Implemented in [#1158](https://github.com/utPLSQL/utPLSQL/pull/1158), resolves [#1151](https://github.com/utPLSQL/utPLSQL/issues/1151) +- Fixed confusing typo in documentation. Resolves [#1154](https://github.com/utPLSQL/utPLSQL/issues/1154) +- Moved build process from travis-ci.org to travis-ci.com. Implemented in [#1152](https://github.com/utPLSQL/utPLSQL/pull/1152) +- Added an example of reporter reporting all expectations, not only the failing ones. Implemented in [#1092](https://github.com/utPLSQL/utPLSQL/pull/1092) + +--- + +## [v3.1.10] - 2020-02-23 + +## Enhancements + +- utPLSQL test runner is now validating arguments of `--%throws` annotations at runtime [#721](https://github.com/utPLSQL/utPLSQL/issues/721) [#1033](https://github.com/utPLSQL/utPLSQL/issues/1033) +- Documented limitations of insignificant spaces comparison in compound data [#880](https://github.com/utPLSQL/utPLSQL/issues/880) +- utPLSQL will now detect empty annotation cache for schema even with DLL trigger enabled [#975](https://github.com/utPLSQL/utPLSQL/issues/975) +- Order of procedures and annotation now determines default order of tests in suite [#1036](https://github.com/utPLSQL/utPLSQL/issues/1036) + +## Bug fixes + +- Nested contexts are now properly identified [#1034](https://github.com/utPLSQL/utPLSQL/issues/1034) +- TeamCity test reporter is now including error message [#1045](https://github.com/utPLSQL/utPLSQL/issues/1045) + +## Internal improvements + +- All self-tests for utPLSQL framework can now be executed using test-owner schema [#969](https://github.com/utPLSQL/utPLSQL/issues/969) +- Misleading rollback warning is no longer showing when running self-tests for utPLSQL [#982](https://github.com/utPLSQL/utPLSQL/issues/982) + +--- + +## [v3.1.9] - 2019-11-10 + +## New features + +- Added ability to define [nested contexts](https://github.com/utPLSQL/utPLSQL/blob/v3.1.9/docs/userguide/annotations.md#context) within test suite [#938](https://github.com/utPLSQL/utPLSQL/issues/938) +- Added ability to [exclude tests/suites by tags](https://github.com/utPLSQL/utPLSQL/blob/v3.1.9/docs/userguide/annotations.md#excluding-testssuites-by-tags) when invoking a test-run [#1007](https://github.com/utPLSQL/utPLSQL/issues/1007) [#983](https://github.com/utPLSQL/utPLSQL/issues/983) +- Added new annotation [`--%name`](https://github.com/utPLSQL/utPLSQL/blob/v3.1.9/docs/userguide/annotations.md#name) to allow for providing custom name for contexts [#1016](https://github.com/utPLSQL/utPLSQL/issues/1016) + +## Important changes + +The value of `--%context` annotation is no longer representing context `name`. +This value is now context description (displayname). +With this change, the `--%context` annotation is now aligned with `--%test` and `--%suite` annotation syntax. + +New annotation `--%name` was introduced to facilitate naming of contexts. + +## Enhancements + +- Improved documentation for running tests +- Improved documentation for tags [#1003](https://github.com/utPLSQL/utPLSQL/issues/1003) +- Improved documentation for annotations + +## Bug fixes + +- Fixed bug with bad stacktrace showing in failing/erroring test [#1000](https://github.com/utPLSQL/utPLSQL/issues/1000) +- Fixed issue with lack of validation for context name [#966](https://github.com/utPLSQL/utPLSQL/issues/966) +- Fixed problem with install script privilege check for installation with DDL trigger [#992](https://github.com/utPLSQL/utPLSQL/issues/992) +- Fixed issue with some common column names causing cursor comparison to fail [#997](https://github.com/utPLSQL/utPLSQL/issues/997) +- Fixed issue with invocation of standalone expectations on cursor [#998](https://github.com/utPLSQL/utPLSQL/issues/998) + +## Internal improvements + +- Fixed runability of utplsql self-tests [#968](https://github.com/utPLSQL/utPLSQL/issues/968) + +--- + +## [v3.1.8] - 2019-09-03 + +## New features + +- Added support for [session context](https://github.com/utPLSQL/utPLSQL/blob/v3.1.8/docs/userguide/annotations.md#sys_context) to provide test and suite information during test run [#963](https://github.com/utPLSQL/utPLSQL/issues/963) [#781](https://github.com/utPLSQL/utPLSQL/issues/781) +- Added ability to [invoke expectations without running framework](https://github.com/utPLSQL/utPLSQL/blob/v3.1.8/docs/userguide/expectations.md#running-expectations-outside-utplsql-framework) [#956](https://github.com/utPLSQL/utPLSQL/issues/956) [#963](https://github.com/utPLSQL/utPLSQL/issues/963) +- Failing expectations are now reported with call stack [#967](https://github.com/utPLSQL/utPLSQL/issues/967) [#963](https://github.com/utPLSQL/utPLSQL/issues/963) + +## Enhancements + +- Improved framework table private data protection [#922](https://github.com/utPLSQL/utPLSQL/issues/922) [#954](https://github.com/utPLSQL/utPLSQL/issues/954) +- Improved install process. It is now unified for installation with both public and private synonyms [#957](https://github.com/utPLSQL/utPLSQL/issues/957) [#954](https://github.com/utPLSQL/utPLSQL/issues/954) +- Improved reporting of warnings for integration with SQLDeveloper [#964](https://github.com/utPLSQL/utPLSQL/issues/964) +- Improved query to retrieve coverage sources [#981](https://github.com/utPLSQL/utPLSQL/issues/981) [#970](https://github.com/utPLSQL/utPLSQL/issues/970) +- Improved security around malicious utPLSQL owner name [#920](https://github.com/utPLSQL/utPLSQL/issues/920) + +## Bug fixes + +- Fixed cursor comparison on Oracle 11.2 [#947](https://github.com/utPLSQL/utPLSQL/issues/947) +- Fixed issue with retrieving suite data [#977](https://github.com/utPLSQL/utPLSQL/issues/977) [#974](https://github.com/utPLSQL/utPLSQL/issues/974) [#978](https://github.com/utPLSQL/utPLSQL/issues/978) +- Application context is now reset in session after test run [#951](https://github.com/utPLSQL/utPLSQL/issues/951) + +--- + +## [v3.1.7] - 2019-06-18 + +## New features + +- Added support for [comparing `json`](https://github.com/utPLSQL/utPLSQL/blob/v3.1.7/docs/userguide/expectations.md#comparing-json-objects) in Oracle 12.2 and above [#924](https://github.com/utPLSQL/utPLSQL/issues/924) +- Introduced [`tag` annotation](https://github.com/utPLSQL/utPLSQL/blob/v3.1.7/docs/userguide/annotations.md#tags) to enable tagging of tests and suites [#66](https://github.com/utPLSQL/utPLSQL/issues/66) +- Added support for [random order](https://github.com/utPLSQL/utPLSQL/blob/v3.1.7/docs/userguide/running-unit-tests.md#random-order) of test execution [#422](https://github.com/utPLSQL/utPLSQL/issues/422) + +## Enhancements + +- Added optional [install with DDL trigger](https://github.com/utPLSQL/utPLSQL/blob/v3.1.7/docs/userguide/install.md#installation-with-ddl-trigger) to speed up framework start [#901](https://github.com/utPLSQL/utPLSQL/issues/901) +- Removed dependency on `dbms_utility.name_resolve` [#569](https://github.com/utPLSQL/utPLSQL/issues/569) [#885](https://github.com/utPLSQL/utPLSQL/issues/885) +- New output buffer table structures improving performance and addressing timeout issues [#915](https://github.com/utPLSQL/utPLSQL/issues/915) + +## Bug fixes + +- Fixed `ut_realtime_reporter` missing warnings in test and suite output structures [#936](https://github.com/utPLSQL/utPLSQL/issues/936) +- Fixed output_buffer purging error [#934](https://github.com/utPLSQL/utPLSQL/issues/934) +- Fixed `join_by / exclude / include` invalid syntax on collection in anydata compare [#912](https://github.com/utPLSQL/utPLSQL/issues/912) +- Fixed `ut_junit_reporter` producing invalid output on failing tests with long failure message [#927](https://github.com/utPLSQL/utPLSQL/issues/927) +- Fixed `ut_sonar_test_reporter` producing invalid output on failing tests with long failure message [#925](https://github.com/utPLSQL/utPLSQL/issues/925) +- Fixed `ut_coverage_cobertura_reporter` producing wrong line breaks which breaks the xml validation against DTD [#917](https://github.com/utPLSQL/utPLSQL/issues/917) +- Fixed `exclude` option for ref cursor where column order was not resolved correctly [#911](https://github.com/utPLSQL/utPLSQL/issues/911) +- Fixed `unordered` option for ref cursor with null values [#914](https://github.com/utPLSQL/utPLSQL/issues/914) +- Fixed number precision when selecting from dual [#907](https://github.com/utPLSQL/utPLSQL/issues/907) +- Fixed ref cursor errors with generated column names [#902](https://github.com/utPLSQL/utPLSQL/issues/902) +- Fixed `ORA-00907` when comparing ref cursors with BINARY_ columns [#899](https://github.com/utPLSQL/utPLSQL/issues/899) +- Fixed wrong results when comparing CLOBs with `to_be_like` in Oracle Database 11.2.0.4 due to Oracle Bug 14402514 [#891](https://github.com/utPLSQL/utPLSQL/issues/891) +- Fixed performance issue with code coverage report on huge PL/SQL code base [#882](https://github.com/utPLSQL/utPLSQL/issues/882) + +## Documentation improvements + +- Added install instructions for DDL trigger [#874](https://github.com/utPLSQL/utPLSQL/issues/874) + +## Internal enhancements + +- Fixed SQL vulnerability on all input parameters used in dynamic SQL and PL/SQL [#921](https://github.com/utPLSQL/utPLSQL/issues/921) +- Fixed message id in output buffer [#916](https://github.com/utPLSQL/utPLSQL/issues/916) +- Included 19c database in self testing [#909](https://github.com/utPLSQL/utPLSQL/issues/909) +- Introduced testing with multiple schemas and different grants [#893](https://github.com/utPLSQL/utPLSQL/issues/893) +- Fixed installation script warnings [#879](https://github.com/utPLSQL/utPLSQL/issues/879) + +--- + +## [v3.1.6] - 2019-03-24 + +Bugfix release for `v3.1.5` + +## Bug fixes + +- Fixed a bug in release `3.1.5` where `to_equal` matcher was failing due to privileges when comparing non sql diffable types [#870](https://github.com/utPLSQL/utPLSQL/issues/870) + +## Improvements +- Reduced number of information displaying about user defined type. We will now display only type name instead of full structure [#866](https://github.com/utPLSQL/utPLSQL/issues/866) + +--- + +## [v3.1.5] - 2019-03-20 + +Bugfix release for `v3.1.4` + +## Bug fixes + +- Fixed a bug in release `3.1.4` where `to_be_empty` matcher was failing due to privileges [#864](https://github.com/utPLSQL/utPLSQL/issues/864) + +--- + +## [v3.1.4] - 2019-03-19 + +**This release contains a bug that is fixed by release 3.1.5** +Please use release 3.1.5 rather than this release. + +## New features + +- Added `to_contain` matcher for collections and cursors [#79](https://github.com/utPLSQL/utPLSQL/issues/79) +- Added `unordered_columns` (`uc`) option for cursor comparison to ignore the order of the columns [#779](https://github.com/utPLSQL/utPLSQL/issues/779) +- Added `ut_debug_reporter` for debug logging [#480](https://github.com/utPLSQL/utPLSQL/issues/480) +- Added `ut_realtime_reporter` for utPLSQL-SQLDeveloper extension [#795](https://github.com/utPLSQL/utPLSQL/issues/795) + +## Important Changes + +- Due to improvements of the cursor comparison, it is now necessary to use `ut.set_nls()` before creating the cursor and `ut.reset_nls()` after the __expectation__ when comparing dates. [More info in the docs](https://github.com/utPLSQL/utPLSQL/blob/v3.1.4/docs/userguide/expectations.md#comparing-cursor-data-containing-date-fields) + +## Enhancements + +- Improved performance of cursor comparison [#780](https://github.com/utPLSQL/utPLSQL/issues/780) +- Added support for installation on databases with block size < 8KB [#848](https://github.com/utPLSQL/utPLSQL/issues/848) +- Added initial timeout to `ut_output_buffer` [#840](https://github.com/utPLSQL/utPLSQL/issues/840) +- Enhanced performance of `get_reporters_list` function [#814](https://github.com/utPLSQL/utPLSQL/issues/814) +- Moved calls of `dbms_lock.sleep` to `dbms_session` for newer DB versions [#806](https://github.com/utPLSQL/utPLSQL/issues/806) +- utPLSQL coverage will now work without re-install after DB-upgrade from 12.1 to 12.2 [#803](https://github.com/utPLSQL/utPLSQL/issues/803) + +## Bug fixes + +- Fixed problem with REGEXP in annotation parsing with NLS CANADIAN FRENCH [#844](https://github.com/utPLSQL/utPLSQL/issues/844) +- Fixed issue with Rollback to savepoint failing on distributed transaction [#839](https://github.com/utPLSQL/utPLSQL/issues/839) +- Fixed reporting of differences when comparing collections scalar values [#835](https://github.com/utPLSQL/utPLSQL/issues/835) +- Fixed issue with test run failing due to too many transaction invalidators [#834](https://github.com/utPLSQL/utPLSQL/issues/834) +- Fixed randomly occurring error during cursor comparison [#827](https://github.com/utPLSQL/utPLSQL/issues/827) +- utPLSQL install script will now support special characters in passwords [#804](https://github.com/utPLSQL/utPLSQL/issues/804) + +## Documentation improvements + +- Fixed documentation examples for context annotation [#851](https://github.com/utPLSQL/utPLSQL/issues/851) +- Added description on how to check version of utPLSQL [#822](https://github.com/utPLSQL/utPLSQL/issues/822) + +## Internal enhancements + +- Implemented Sonar analysis on DBA Views [#850](https://github.com/utPLSQL/utPLSQL/issues/850) +- Finished migration from old-tests [#475](https://github.com/utPLSQL/utPLSQL/issues/475) +- Fixed shell scripts to support multiple unix dialects (especially for macOS) [#796](https://github.com/utPLSQL/utPLSQL/issues/796) +- Added info on project support from Redgate [#841](https://github.com/utPLSQL/utPLSQL/issues/841) +- Added `code_of_conduct` [#836](https://github.com/utPLSQL/utPLSQL/issues/836) +- Added issue templates [#842](https://github.com/utPLSQL/utPLSQL/issues/842) +- Added utPLSQL logo [#845](https://github.com/utPLSQL/utPLSQL/issues/845) + +--- + +## [v3.1.3] - 2018-11-20 + +## New features + +- added function `ut_runner.is_test` [#788](https://github.com/utPLSQL/utPLSQL/issues/788) +- added function `ut_runner.is_suite` [#787](https://github.com/utPLSQL/utPLSQL/issues/787) +- added function `ut_runner.has_suites` [#786](https://github.com/utPLSQL/utPLSQL/issues/786) +- added ability to disable automatic rollback for a test-run [#784](https://github.com/utPLSQL/utPLSQL/issues/784) +- when invoked with package name, utPLSQL will now run only tests from specified package even if package has child packages by suitepath [#776](https://github.com/utPLSQL/utPLSQL/issues/776) + +## Enhancements + +- Improved performance of schema-scanning and utPLSQL startup [#778](https://github.com/utPLSQL/utPLSQL/issues/778) +- Improved performance of output-buffer [#777](https://github.com/utPLSQL/utPLSQL/issues/777) +- Improved documentation to mention ability to pass client encoding for HTML & XML reports [#775](https://github.com/utPLSQL/utPLSQL/issues/775) +- Improved documentation for cursor comparison to mention challenges with TIMESTAMP bind variables + +## Bug-fixes + +- utPLSQL code coverage will now work properly with long object names [#716](https://github.com/utPLSQL/utPLSQL/issues/716) +- utPLSQL installation will now also work properly, when user performing the install has `ANY` grants [#737](https://github.com/utPLSQL/utPLSQL/issues/737) +- fixed documentation bug for `--%context` with `--%displayname` [#726](https://github.com/utPLSQL/utPLSQL/issues/726) +- fixed Teamcity reporter issues with missing escape for some characters and long messages [#747](https://github.com/utPLSQL/utPLSQL/issues/747) +- fixed issue with sonar test results reporter when contexts are used [#749](https://github.com/utPLSQL/utPLSQL/issues/749) +- fixed issue with ORA-07455 getting thrown on cursor comparison [#752](https://github.com/utPLSQL/utPLSQL/issues/752) +- fixed issue with wrong failure message for unordered data [#764](https://github.com/utPLSQL/utPLSQL/issues/764) +- fixed missing privilege issue for unordered/join-by cursor data comparison [#765](https://github.com/utPLSQL/utPLSQL/issues/765) [#770](https://github.com/utPLSQL/utPLSQL/issues/770) + +## Internal enhancements + +- added suite-level cache to allow for faster retrieval of suite contents and enable implementation of additional features [#783](https://github.com/utPLSQL/utPLSQL/issues/783) + +--- + +## [v3.1.2] - 2018-07-22 + +## New features + +- Added ability to join and compare cursor content by specific columns (PK/UK) [#453](https://github.com/utPLSQL/utPLSQL/issues/453) +- Added support for comma separated list of suite paths/packages when calling `ut.run` [#479](https://github.com/utPLSQL/utPLSQL/issues/479) +- Added ability to run a test package that got invalidated due to dependency invalidation [#489](https://github.com/utPLSQL/utPLSQL/issues/489) +- Added support for package level constants and predefined exceptions in `--%throws` annotation [#685](https://github.com/utPLSQL/utPLSQL/issues/685) +- Added support for standalone `--%beforeall`, `--%beforeeach`, `--%afterall`, `--%aftereach` annotations with list of procedures to execute [#649](https://github.com/utPLSQL/utPLSQL/issues/649) +- Added support for list of procedure names in before/after annotations [#649](https://github.com/utPLSQL/utPLSQL/issues/649) +- Added support for BLOB/CLOB in `is_empty()` matcher [#707](https://github.com/utPLSQL/utPLSQL/issues/707) + +## Enhancements + +- utPLSQL will now provide additional warnings, when unsupported annotations are found in a unit test suite package [#624](https://github.com/utPLSQL/utPLSQL/issues/624) +- utPLSQL will now produce valid XML in UT_JUNIT_REPORTER when dbms_output or test results contain ` **Important notice:** > We do our best to keep utPLSQL covered with unit tests. @@ -145,8 +145,8 @@ Every new functionality needs to be documented by unit tests that cover both hap To run a full suite of unit tests execute: ```bash -development/env.sh -test/install_and_run_tests.sh +. ./development/env.sh +./test/install_and_run_tests.sh ``` You can review the results of tests as well as see information about code coverage in `./coverage.html` file. @@ -162,45 +162,20 @@ Dependencies to `ut_util` are not shown as most of modules are depending on it. ## Build Environment -We are using private docker images to test utPLSQL for our Travis CI builds. The following versions of Oracle Database are being used. +We are using a combination of private and publicly available docker images to test utPLSQL. The following versions of Oracle Database are used for testing: -* 11g XE R2 -* 12c SE R1 -* 12c SE R2 -* 18c SE +* 19c-Standard Edition (private image) +* 21-free +* 23-free -These images are based on the slimmed versions [official dockerfiles released by Oracle](https://github.com/utPLSQL/docker-scripts), but due to licensing restrictions, we can't make the images public. -You can build your own and use it locally, or push to a private docker repository. - -The build steps are simple if you already have some experience using Docker. You can find detailed information about how to build your own image with a running database in: [example of creating an image with pre-built DB](https://github.com/oracle/docker-images/blob/master/OracleDatabase/samples/prebuiltdb/README.md) - -> You can find more info about the official Oracle images on the [Oracle Database on Docker](https://github.com/oracle/docker-images/tree/master/OracleDatabase) GitHub page. - -> If you are new to Docker, you can start by reading the [Getting Started With Docker](https://docs.docker.com/engine/getstarted/) docs. - -### Docker Build Notes - -* You need to comment out the VOLUME line. This step is required, because volumes are not saved when using `docker commit` command. -* When the build process is complete, you will run the container to install the database. Once everything is set up and you see the message "DATABASE IS READY!", you may change the password and stop the running container. After the container is stopped, you can safely commit the container. -* You can use the --squash experimental docker tag to reduce the image size. Example: -``` -docker build --force-rm --no-cache --squash -t oracle/db-prebuilt . -``` - -Travis will use your Docker Hub credentials to pull the private images, and the following secure environment variables must be defined. - -Variable | Description ----------|------------ -**DOCKER_USER**
**DOCKER_PASSWORD** | _Your Docker Hub website credentials. They will be used to pull the private database images._ +The Free varsions are publicly available and you can set up your local development environment to use one of from [here](https://github.com/gvenzl/oci-oracle-free) -### SQLCL +These [private image]((https://github.com/utPLSQL/docker-scripts) is a slimmed version of [the official Oracle database dockerfiles](https://github.com/oracle/docker-images/tree/master/OracleDatabase) . Due to licensing restrictions, the image cannot be public. -Our build configuration uses SQLCL to run the scripts, and you need to configure a few additional secure environment variables. After the first build, the downloaded file will be cached. - -Variable | Description ----------|------------ -**ORACLE_OTN_USER
ORACLE_OTN_PASSWORD** | _Your Oracle website credentials. They will be used to download SQLCL._ +You can build your own and use it locally, or push to a private docker repository. +The build steps are simple if you already have some experience using Docker. You can find detailed information about how to build your own image with a running database in: [example of creating an image with pre-built DB](https://github.com/oracle/docker-images/blob/master/OracleDatabase/samples/prebuiltdb/README.md) +> If you are new to Docker, you can start by reading the [Getting Started With Docker](https://docs.docker.com/get-started/) docs. ## New to GIT diff --git a/VERSION b/VERSION index d0237db61..19db2fb66 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v3.1.13-develop +v3.2.4-develop diff --git a/development/cleanup.sh b/development/cleanup.sh index 33358302a..d36f21540 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 @@ -29,11 +29,13 @@ drop user ut3_select_catalog_user cascade; drop user ut3_select_any_table_user cascade; drop user ut3_execute_any_proc_user cascade; +set serveroutput on begin for i in ( select decode(owner,'PUBLIC','drop public synonym "','drop synonym "'||owner||'"."')|| synonym_name ||'"' drop_orphaned_synonym, owner||'.'||synonym_name syn from dba_synonyms a where not exists (select 1 from dba_objects b where (a.table_name=b.object_name and a.table_owner=b.owner or b.owner='SYS' and a.table_owner=b.object_name) ) - and a.table_owner not in ('SYS','SYSTEM') + and a.table_owner not in (select username from dba_users where oracle_maintained = 'Y') + and a.origin_con_id = sys_context('userenv', 'con_id') ) loop execute immediate i.drop_orphaned_synonym; dbms_output.put_line('synonym '||i.syn||' dropped'); 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..87869d581 100755 --- a/development/refresh_sources.sh +++ b/development/refresh_sources.sh @@ -1,18 +1,19 @@ -#!/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} # clone utPLSQL main branch from upstream into utPLSQL sub-directory of your project git clone --depth=1 --branch=${SELFTESTING_BRANCH:-main} https://github.com/utPLSQL/utPLSQL.git ${UTPLSQL_DIR:-utPLSQL_latest_release} +rm -rf ${UTPLSQL_DIR:-utPLSQL_latest_release}/.git 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..99c3f7114 100644 --- a/development/releasing.md +++ b/development/releasing.md @@ -1,29 +1,25 @@ -## Release process +# 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 do the following: + - create a new tag + - generate html documentation to be included in the release file. + - publish documentation for the new version into utPLSQL site + - upload release artifacts (`zip` and `tar.gz` files along with `sha256` files) + - update the CHANGELOG.md file with the release details + - publish an announcement about the release in the utPLSQL Organization Discussions + - publish a release post on utplsql.github.io (new post in `docs/_posts`, updated `mkdocs.yml` nav and `docs/index.md`) + - After the release process is finished, manually increase the version number in the `VERSION` file on `develop` branch to start the next release version. + - Send an announcement about the release on:Twitter(X), BlueSky, LinkedIn account about the 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. - -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 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..fdbed4b6b 100755 --- a/development/template.env.sh +++ b/development/template.env.sh @@ -1,10 +1,10 @@ -#!/usr/bin/env bash +#!/bin/bash -export SQLCLI=sql # For sqlcl client -#export SQLCLI=sqlplus # For sqlplus client -export CONNECTION_STR=127.0.0.1:1521/xe ORACLE_VERSION=11g-r2-xe# Adjust the connect string -export ORACLE_PWD=oracle # Adjust your local SYS password -export UTPLSQL_CLI_VERSION="3.1.6" +export SQLCLI=sql # For sqlcl client +#export SQLCLI=sqlplus # For sqlplus client +export CONNECTION_STR=127.0.0.1:1521/xe # Adjust the connect string +export ORACLE_PWD=oracle # Adjust your local SYS password +export UTPLSQL_CLI_VERSION="v3.1.8" export SELFTESTING_BRANCH=develop export UTPLSQL_DIR="utPLSQL_latest_release" diff --git a/docs/about/authors.md b/docs/about/authors.md deleted file mode 100644 index 1de762d74..000000000 --- a/docs/about/authors.md +++ /dev/null @@ -1,26 +0,0 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -### utPLSQL v3 Major Contributors - -**Listed Alphabetically** - -| Name | GitHub account -| ---------------- | -------------- -| David Pyke | [Shoelace](https://github.com/Shoelace) -| Jacek Gebal | [jgebal](https://github.com/jgebal) -| Lukasz Wasylow | [lwasylow](https://github.com/lwasylow/) -| Pavel Kaplya | [Pazus](https://github.com/Pazus) -| Robert Love | [rlove](https://github.com/rlove) -| Samuel Nitsche | [pesse](https://github.com/pesse/) -| Vinicius Avellar | [viniciusam](https://github.com/viniciusam/) - - - -Many thanks to all the [contributors](https://github.com/utPLSQL/utPLSQL/graphs/contributors) - -### Special thanks to prior major contributors - -- Steven Feuerstein - Original Author -- Chris Rimmer -- Patrick Barel -- Paul Walker diff --git a/docs/about/license.md b/docs/about/license.md deleted file mode 100644 index 5030fff0a..000000000 --- a/docs/about/license.md +++ /dev/null @@ -1,17 +0,0 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -# Version Information - -**utPLSQL version 3** is licensed under -[Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) - -External code used in the development of this project, but is not required for use. - -| Tool | License | Purpose | -| ---- | --------| --------| -| [Travis-Oracle](https://github.com/cbandy/travis-oracle) | [ISC](https://github.com/cbandy/travis-oracle/blob/master/LICENSE) | Install Oracle for Travis Builds | -| [mkDocs](http://www.mkdocs.org/) | [BSD](http://www.mkdocs.org/about/license/) | Produce HTML version of documentation | - -**Note:** - -Version 1 & 2 of utPLSQL were licensed under GPL, version 3 was a complete rewrite from scratch which a allowed us to change the license to be more permissive. diff --git a/docs/about/project-details.md b/docs/about/project-details.md deleted file mode 100644 index 71178988b..000000000 --- a/docs/about/project-details.md +++ /dev/null @@ -1,18 +0,0 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -# utPLSQL Project Details - -[![chat](http://img.shields.io/badge/GitHub_Project-Active-blue.svg)](https://github.com/utPLSQL/utPLSQL) -[![license](http://img.shields.io/badge/license-apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) -[![chat](http://img.shields.io/badge/chat-slack-blue.svg)](https://join.slack.com/t/utplsql/shared_invite/zt-xwm68udy-4cF_3PNEyczYEbWr38W5ww) -[![twitter](https://img.shields.io/twitter/follow/utPLSQL.svg?style=social&label=Follow)](https://twitter.com/utPLSQL) - - -utPLSQL is open source project [hosted on GitHub](https://github.com/utPLSQL/utPLSQL). - -Contributions, help and constructive feedback is always appreciated. - -If you are interested in helping please read our [contributing guide](https://github.com/utPLSQL/utPLSQL/blob/develop/CONTRIBUTING.md) - - - diff --git a/docs/about/support.md b/docs/about/support.md deleted file mode 100644 index 1c0679440..000000000 --- a/docs/about/support.md +++ /dev/null @@ -1,6 +0,0 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -# How to get support - -- Feel free to post questions, bugs or issues in the [issues area of GitHub](https://github.com/utPLSQL/utPLSQL/issues) -- [Join](https://join.slack.com/t/utplsql/shared_invite/zt-xwm68udy-4cF_3PNEyczYEbWr38W5ww) developers team on [utPLSQL Slack](https://utplsql.slack.com/) 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/compare_version2_to_3.md b/docs/compare_version2_to_3.md index 5f53e41d4..121fafc59 100644 --- a/docs/compare_version2_to_3.md +++ b/docs/compare_version2_to_3.md @@ -37,7 +37,6 @@ There is a [migration tool](https://github.com/utPLSQL/utPLSQL-v2-v3-migration) | **Code Coverage Reporting** | No | Yes | | Html Coverage Report | No | Yes | | Sonar XML Coverage Report | No | Yes | -| Coveralls Json Coverage Report | No | Yes | | Framework Transaction Control | No | Yes - Annotations1 | | **Test Output** | | | | Real-time test execution progress reporting | No | Yes | diff --git a/docs/images/tap_reporter_colored.png b/docs/images/tap_reporter_colored.png new file mode 100644 index 000000000..d4a5e9326 Binary files /dev/null and b/docs/images/tap_reporter_colored.png differ diff --git a/docs/images/tap_reporter_no_color.png b/docs/images/tap_reporter_no_color.png new file mode 100644 index 000000000..e7379a889 Binary files /dev/null and b/docs/images/tap_reporter_no_color.png differ diff --git a/docs/images/tap_reporter_suitepath.png b/docs/images/tap_reporter_suitepath.png new file mode 100644 index 000000000..488c7cfbf Binary files /dev/null and b/docs/images/tap_reporter_suitepath.png differ diff --git a/docs/index.md b/docs/index.md index b79c845f5..a7255f8f8 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.2.4.4532--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/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 000000000..fdfe1a3be --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{# + Sub-repository template override. + Loads the shared utPLSQL.org top navigation bar from the central org site. + The topbar.js file is the single source of truth — update nav links there only. +#} + +{% block extrahead %} + {{ super() }} + + +{% endblock %} 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..413cf27b3 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.2.4.4532--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..838fc0890 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1,31 +1,30 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) - -# Annotations +![version](https://img.shields.io/badge/version-v3.2.4.4532--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. -**Note:** ->Annotations are interpreted only in the package specification and are case-insensitive. We strongly recommend using lower-case annotations as described in this documentation. +!!! note + Annotations are interpreted only in the package specification and are case-insensitive. We strongly recommend using lower-case annotations as described in this documentation. There are two distinct types of annotations, identified by their location in package. - package annotations @@ -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 @@ -47,7 +46,7 @@ package test_package is --%disabled procedure my_first_procedure; - $if dbms_db_version.version >= 12 $then --This is ok - annotation before procedure + $if dbms_db_version.version >= 19 $then --This is ok - annotation before procedure --%test() procedure my_first_procedure; $end @@ -59,7 +58,7 @@ end; ``` Invalid procedure annotations examples: -```sql +```sql linenums="1" package test_package is --%suite @@ -72,7 +71,7 @@ package test_package is procedure proc1; --%test() --This is wrong as there is a compiler directive between procedure and annotation - $if dbms_db_version.version >= 12 $then + $if dbms_db_version.version >= 19 $then procedure proc_12; $end @@ -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 @@ -152,25 +151,23 @@ The `--%suite` annotation denotes PLSQL package as a unit test suite. It accepts an optional description that will be visible when running the tests. When description is not provided, package name is displayed on report. -**Note** ->Package is considered a test-suite only when package specification contains the `--%suite` annotation at the package level. -> ->Some annotations like `--%suite`, `--%test` and `--%displayname` accept parameters. The parameters for annotations need to be placed in brackets. -Values for parameters should be provided without any quotation marks. -If the parameters are placed without brackets or with incomplete brackets, they will be ignored. -> ->Example: `--%suite(The name of suite without closing bracket` ->Example: `--%suite The name of suite without brackets` +!!! note + Package is considered a test-suite only when package specification contains the `--%suite` annotation at the package level. + Some annotations like `--%suite`, `--%test` and `--%displayname` accept parameters. The parameters for annotations need to be placed in brackets. + Values for parameters should be provided without any quotation marks. + If the parameters are placed without brackets or with incomplete brackets, they will be ignored. + Example: `--%suite(The name of suite without closing bracket` + Example: `--%suite The name of suite without brackets` 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 +178,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 +195,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 +220,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 +254,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 +267,7 @@ create or replace package body test_package as end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -282,7 +279,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 +293,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -308,7 +305,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 +320,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -346,7 +343,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 +364,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -380,7 +377,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 +407,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -425,7 +422,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 +443,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -460,10 +457,10 @@ Finished in .005868 seconds ### Beforeall -There are two possible ways to use the `--%beforeall` annotation. +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 +472,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 +489,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 +500,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 +528,10 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` + ``` Tests for a package --- SETUP_STUFF invoked --- @@ -544,9 +543,9 @@ 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. +The execution order of the `--%beforeall` procedures is determined by the annotation position in the 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 +594,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 +611,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 +646,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,15 +668,17 @@ Warnings: Finished in .012158 seconds 2 tests, 0 failed, 0 errored, 0 disabled, 2 warning(s) - ``` +``` +!!! note "Suite scope" + The `--%beforeall` procedrues in parent suite package are always part of all suite packages that are children on the suitepath. ### Afterall 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] @@ -754,6 +759,10 @@ Finished in .014161 seconds 2 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) ``` +!!! note "Suite scope" + The `--%afterall` procedrues in parent suite package are always part of all suite packages that are children on the suitepath. + + ### Beforeeach The procedure annotated as `--%beforeeach` is getting executed before each test in a suite. @@ -762,7 +771,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 +783,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 +792,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 +813,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 +854,10 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` + ``` Tests for a package ---SETUP_STUFF invoked --- @@ -861,6 +872,9 @@ Finished in .014683 seconds 2 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) ``` +!!! note "Package scope" + Unlike `--%beforeall`, the `--%beforeeach` annotation is only applicable within the scope of current test suite package. The `--%beforeeach` annotations from parent suite package are ignored. + See [beforeall](#Beforeall) for more examples. ### Aftereach @@ -873,7 +887,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 +899,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 +908,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 +927,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 +967,7 @@ create or replace package body test_package as end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -969,11 +984,15 @@ Finished in .018115 seconds 2 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) ``` +!!! note "Package scope" + Unlike `--%afterall`, the `--%aftereach` annotation is only applicable within the scope of current test suite package. The `--%aftereach` annotations from parent suite package are ignored. + 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 +1003,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 +1013,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 +1061,7 @@ create or replace package body test_package as end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -1062,6 +1083,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 +1092,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 +1101,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 +1149,7 @@ create or replace package body test_package as end; / ``` -```sql +```sql linenums="1" exec ut.run('test_package'); ``` ``` @@ -1160,6 +1184,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 +1202,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 +1259,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 +1383,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 +1406,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 +1496,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 +1529,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 +1582,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 +1613,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 +1667,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 +1686,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 +1705,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,10 +1720,13 @@ 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. +!!! note + Only the `beforeall` and `afterall` hooks of the parent suite are executed for the suite package. The `beforeeach` and `aftereach` hooks of the suite package are not executed. ### Rollback @@ -1789,23 +1752,24 @@ Doing so allows your tests to use the framework's automatic transaction control When you are testing code that performs explicit or implicit commits, you may set the test procedure to run as an autonomous transaction with `pragma autonomous_transaction`. Keep in mind that when your test runs as autonomous transaction it will not see the data prepared in a setup procedure unless the setup procedure committed the changes. -**Note** -> The `--%suitepath` annotation, when used, must be provided with a value of path. -> The path in suitepath cannot contain spaces. Dot (.) identifies individual elements of the path. -> -> Example: `--%suitepath(org.utplsql.core.utils)` -> +!!! note + The `--%rollback(manual)` annotation is only relevant for suites that use shared setup/cleanup with beforeall, + The `--%suitepath` annotation, when used, must be provided with a value of path. + The path in suitepath cannot contain spaces. Dot (.) identifies individual elements of the path. + Example: `--%suitepath(org.utplsql.core.utils)` + ### 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)` -- [predefined oracle exceptions](https://docs.oracle.com/cd/E11882_01/timesten.112/e21639/exceptions.htm#CIHFIGFE) - example `--%throws(no_data_found)` +- variables of type `exception` defined in a package specification - example `--%throws(exc_pkg.c_exception_no_variable)` +- variables or constants of containing a valid negative exception number defined in a package specification - example `--%throws(exc_pkg.c_some_exception)` +- [predefined oracle exceptions](https://docs.oracle.com/en//database/oracle/oracle-database/19/lnpls/predefined-exceptions.html) - example `--%throws(no_data_found)` -The annotation is ignored, when no valid arguments are provided. Examples of invalid annotations `--%throws()`,`--%throws`, `--%throws(abe, 723pf)`. +The annotation is ignored when no valid arguments are provided. Examples of invalid annotations `--%throws()`,`--%throws`, `--%throws(abe, 723pf)`. If `--%throws` annotation is specified with arguments and no exception is raised, the test is marked as failed. @@ -1816,22 +1780,30 @@ 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. -​ + +Syntax: `--%throws( [[schema.]package.]exception [, ... ])` + +The exception name can be provided with or without the schema and package name. The package name is required only when the exception variable is located in another package than the unit test package. The schema name is required only when the exception variable is located in a package in another schema. + 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'; - c_e_option3 number := -20202; - - e_option4 exception; - pragma exception_init(e_option4, -20203); + c_e_option3 integer := -20202; + e_initialized_exception exception; + pragma exception_init(e_initialized_exception, -20203); + + e_uninitialized_exception exception; end; / create or replace package example_pgk as + e_local_exception exception; + e_local_exception_num constant integer := -20123; + --%suite(Example Throws Annotation) --%test(Throws one of the listed exceptions) @@ -1862,10 +1834,22 @@ create or replace package example_pgk as --%throws(exc_pkg.c_e_option3) procedure raised_option3_exception; - --%test(Throws package exception option4) - --%throws(exc_pkg.e_option4) - procedure raised_option4_exception; + --%test(Throws exception associated with exception number) + --%throws(exc_pkg.e_initialized_exception) + procedure raised_initialized_exception; + + --%test(Throws uninitialized exception) + --%throws(exc_pkg.e_uninitialized_exception) + procedure raised_uninitialized_exception; + --%test(Throws exception local to unit tests package) + --%throws(e_local_exception) + procedure raised_local_exception; + + --%test(Throws exception local to unit tests package with exception number) + --%throws(e_local_exception_num) + procedure raised_local_exception_num; + --%test(Raise name exception) --%throws(DUP_VAL_ON_INDEX) procedure raise_named_exc; @@ -1876,6 +1860,7 @@ create or replace package example_pgk as end; / + create or replace package body example_pgk is procedure raised_one_listed_exception is begin @@ -1912,11 +1897,28 @@ create or replace package body example_pgk is raise_application_error(exc_pkg.c_e_option3, 'Test error'); end; - procedure raised_option4_exception is + procedure raised_initialized_exception is begin - raise exc_pkg.e_option4; + raise exc_pkg.e_initialized_exception; end; + procedure raised_uninitialized_exception is + begin + raise exc_pkg.e_uninitialized_exception; + end; + + procedure raised_local_exception is + pragma autonomous_transaction; + begin + raise e_local_exception; + end; + + procedure raised_local_exception_num is + pragma autonomous_transaction; + begin + raise_application_error(e_local_exception_num, 'Test error'); + end; + procedure raise_named_exc is begin raise DUP_VAL_ON_INDEX; @@ -1935,53 +1937,60 @@ exec ut3.ut.run('example_pgk'); Running the test will give report: ``` Example Throws Annotation - Throws one of the listed exceptions [.002 sec] - Throws different exception than expected [.002 sec] (FAILED - 1) - Throws different exception than listed [.003 sec] (FAILED - 2) - Gives failure when an exception is expected and nothing is thrown [.002 sec] (FAILED - 3) - Throws package exception option1 [.003 sec] - Throws package exception option2 [.002 sec] - Throws package exception option3 [.002 sec] - Throws package exception option4 [.002 sec] - Raise name exception [.002 sec] - Invalid throws annotation [.002 sec] - + Throws one of the listed exceptions [,026 sec] + Throws different exception than expected [,009 sec] (FAILED - 1) + Throws different exception than listed [,014 sec] (FAILED - 2) + Gives failure when an exception is expected and nothing is thrown [,016 sec] (FAILED - 3) + SUCCESS + Actual: 1 (number) was expected to equal: 1 (number) + Throws package exception option1 [,007 sec] + Throws package exception option2 [,008 sec] + Throws package exception option3 [,007 sec] + Throws exception associated with exception number [,006 sec] + Throws uninitialized exception [,006 sec] + Throws exception local to unit tests package [,009 sec] + Throws exception local to unit tests package with exception number [,011 sec] + Raise name exception [,006 sec] + Invalid throws annotation [,004 sec] + Failures: - + 1) raised_different_exception Actual: -20143 was expected to equal: -20144 ORA-20143: Test error - ORA-06512: at "UT3.EXAMPLE_PGK", line 9 - ORA-06512: at "UT3.EXAMPLE_PGK", line 9 - ORA-06512: at line 6 - + ORA-06512: at "UT3_TESTER.EXAMPLE_PGK", line 9 + ORA-06512: at "UT3_TESTER.EXAMPLE_PGK", line 9 + ORA-06512: at line 7 + 2) raised_unlisted_exception Actual: -20143 was expected to be one of: (-20144, -1, -20145) ORA-20143: Test error - ORA-06512: at "UT3.EXAMPLE_PGK", line 14 - ORA-06512: at "UT3.EXAMPLE_PGK", line 14 - ORA-06512: at line 6 - + ORA-06512: at "UT3_TESTER.EXAMPLE_PGK", line 14 + ORA-06512: at "UT3_TESTER.EXAMPLE_PGK", line 14 + ORA-06512: at line 7 + 3) nothing_thrown Expected one of exceptions (-20459, -20136, -20145) but nothing was raised. - - + + Warnings: - - 1) example_pgk + + 1) example_pgk.raised_one_listed_exception Invalid parameter value "bad" for "--%throws" annotation. Parameter ignored. - at "UT3.EXAMPLE_PGK.RAISED_ONE_LISTED_EXCEPTION", line 6 + at package "UT3_TESTER.EXAMPLE_PGK.RAISED_ONE_LISTED_EXCEPTION", line 8 + 2) example_pgk "--%throws" annotation requires a parameter. Annotation ignored. - at "UT3.EXAMPLE_PGK.BAD_THROWS_ANNOTATION", line 42 - -Finished in .025784 seconds -10 tests, 3 failed, 0 errored, 0 disabled, 2 warning(s) + at package "UT3_TESTER.EXAMPLE_PGK.BAD_THROWS_ANNOTATION", line 57 + +Finished in ,138276 seconds +13 tests, 3 failed, 0 errored, 0 disabled, 2 warning(s) + ``` ## Order of execution -```sql +```sql linenums="1" create or replace package test_employee_pkg is --%suite(Employee management) @@ -2103,46 +2112,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 +2214,7 @@ end; / ``` -```sql +```sql linenums="1" exec ut.run('test_call'); ``` @@ -2234,14 +2247,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..c3f026d6f 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.2.4.4532--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..a069fb598 100644 --- a/docs/userguide/coverage.md +++ b/docs/userguide/coverage.md @@ -1,28 +1,34 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.2.4.4532--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. +utPLSQL comes with a built-in coverage reporting engine. The code coverage reporting combines data from package DBMS_PROFILER and DBMS_PLSQL_CODE_COVERAGE. 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/) * `ut_coverage_cobertura_reporter` - generates a basic Cobertura coverage (http://cobertura.sourceforge.net/xml/coverage-04.dtd) report providing detailed information on code coverage with line numbers. This coverage report is designed to be consumed by services like TFS and Jenkins. Check this link for an example of XML generated by Java: https://raw.githubusercontent.com/jenkinsci/cobertura-plugin/master/src/test/resources/hudson/plugins/cobertura/coverage-with-data.xml ## 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). +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/en/database/oracle/oracle-database/19/arpls/DBMS_PROFILER.html#GUID-DEE5EA97-14AA-4AF2-A5F7-4AB1D004B99F). 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 +43,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,14 +62,15 @@ 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 -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. +### Partial Coverage -utPLSQL installation automatically creates tables needed by `dbms_plsql_code_coverage` on databases in versions above 12c Release 1. -Due to security model of `dbms_plsql_code_coverage` package, utPLSQL grants access to those tables and creates synonyms for those tables. -The access and synonyms will be public when using the headless installation. This approach avoids complexity of forcing every user of utPLSQL framework to create tables on their own. +#### What it does +Partial coverage combines two data sources — the profiler and block coverage — to give you a more detailed picture of which parts of your code have been tested. +When a line appears in both sources, and block coverage shows it was only partially covered, the display will reflect that: you'll see the line marked as partially covered, along with how many blocks on that line were executed out of the total. + +#### Setup and permissions +utPLSQL automatically creates the tables required by Oracle's dbms_plsql_code_coverage package — you don't need to create them manually. +Because of how dbms_plsql_code_coverage handles security, utPLSQL also takes care of granting access to those tables and creating the necessary synonyms. In a headless installation, both the grants and synonyms are made public, keeping things simple without requiring every utPLSQL user to set up their own tables. Sample output: @@ -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,26 +299,27 @@ 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 -utPLSQL provides reporters that produce reports consumable by external tools like `Sonar`/`SonarCloud` & `Coveralls`. +utPLSQL provides reporters that produce reports consumable by external tools like `Sonar`/`SonarCloud`. -Services like Sonar, Coveralls and others perform analysis based on source code in project files. +Services like Sonar and others perform analysis based on source code in project files. They are abstracted from database, schema names, packages, procedures and functions, and operate on a more generic concept of project source code. 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. +- Project files (database objects) that were not executed at all are not reported as fully uncovered. It is up to the consumer (Sonar) to determine if project file should be considered as 0% coverage or just ignored. In order to successfully use coverage on project files, those files must be mapped to database objects. @@ -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..2a98ce0b4 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.2.4.4532--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..8849a7f47 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.2.4.4532--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,19 +641,17 @@ FAILURE at "anonymous block", line 5 ``` -## be_like +### be_like Validates that the actual value is like the expected expression. Syntax: `ut.expect( a_actual ).to_be_like( a_mask [, a_escape_char] )` -Parameters `a_mask` and `a_escape_char` represent valid parameters of the [Oracle LIKE condition](https://docs.oracle.com/database/121/SQLRF/conditions007.htm#SQLRF52142). - -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`. +Parameters `a_mask` and `a_escape_char` represent valid parameters of the [Oracle LIKE condition](https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Pattern-matching-Conditions.html). 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 +677,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 +705,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 +733,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 +761,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,17 +797,17 @@ FAILURE at "anonymous block", line 11 ``` -## match +### match Validates that the actual value is matching the expected regular expression. Syntax: `ut.expect( a_actual ).to_match( a_pattern [, a_modifiers] );` -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) +Parameters `a_pattern` and `a_modifiers` represent a valid regexp pattern accepted by [Oracle REGEXP_LIKE condition](https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Pattern-matching-Conditions.html#GUID-D2124F3A-C6E4-4CCA-A40E-2FFCABFD8E19) 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 +831,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 +845,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 +912,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 +932,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 +964,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 +992,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 +1029,7 @@ FAILURE **Example 4.** -```sql +```sql linenums="1" declare l_actual ut_varchar2_list; l_expected ut_varchar2_list; @@ -1058,7 +1062,7 @@ FAILURE **Example 5.** -```sql +```sql linenums="1" declare l_actual ut_varchar2_list; l_expected ut_varchar2_list; @@ -1094,7 +1098,7 @@ SUCCESS DEF ``` -## to_be_within of +### to_be_within of Determines whether expected value is within range (tolerance) from another value. @@ -1131,7 +1135,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 +1143,7 @@ end; ``` **Example 2.** -```sql +```sql linenums="1" begin ut.expect(3).to_be_within(1).of_(5); end; @@ -1157,7 +1161,7 @@ Failures: ``` **Example 3.** -```sql +```sql linenums="1" begin ut.expect(sysdate).to_be_within(interval '1' day).of_(sysdate+2); end; @@ -1175,7 +1179,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 +1191,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 +1207,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 @@ -1217,13 +1222,12 @@ utPLSQL is capable of comparing compound data-types including: - Comparison of cursor returning `TIMESTAMP` **columns** against cursor returning `TIMESTAMP` **bind variables** requires variables to be cast to proper precision. This is an Oracle SQL - PLSQL compatibility issue and usage of CAST is the only known workaround for now. See [Comparing cursor data containing TIMESTAMP bind variables](#comparing-cursor-data-containing-timestamp-bind-variables) for examples. - To compare nested table/varray type you need to convert it to `anydata` by using `anydata.convertCollection()` - To compare object type you need to convert it to `anydata` by using `anydata.convertObject()` -- It is possible to compare PL/SQL records, collections, varrays and associative arrays. To compare this types of data, use cursor comparison feature of utPLSQL and TABLE operator in SQL query - - On Oracle 11g Release 2 - pipelined table functions are needed (see section [Implicit (Shadow) Types in this artcile](https://oracle-base.com/articles/misc/pipelined-table-functions)) - - On Oracle 12c and above - use [TABLE function on nested tables/varrays/associative arrays of PL/SQL records](https://oracle-base.com/articles/12c/using-the-table-operator-with-locally-defined-types-in-plsql-12cr1) +- It is possible to compare PL/SQL records, collections, varrays and associative arrays. To compare this types of data, use cursor comparison feature of utPLSQL and [TABLE operator in SQL query](https://oracle-base.com/articles/12c/using-the-table-operator-with-locally-defined-types-in-plsql-12cr1) - utPLSQL is not able to distinguish between NULL and whitespace-only column/attribute value when comparing compound data. This is due to Oracle limitation on of XMLType. 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 +1238,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 +1248,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 +1263,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 +1275,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 +1323,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 +1339,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 +1349,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 +1377,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 +1415,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 +1599,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 +1655,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 +1694,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 +1757,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** +utPLSQL is capable of comparing json data-types of `json_element_t`, 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 19c to 23 AI to enable `json` type compare. ### Notes on comparison of json data @@ -1766,7 +1776,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 +1858,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..059ff9223 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.2.4.4532--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..649d7a6ca 100644 --- a/docs/userguide/install.md +++ b/docs/userguide/install.md @@ -1,27 +1,18 @@ -![version](https://img.shields.io/badge/version-v3.1.13.4015--develop-blue.svg) +![version](https://img.shields.io/badge/version-v3.2.4.4532--develop-blue.svg) -# Supported database versions +## Supported database versions -utPLSQL is continuously tested against following versions of Oracle databases -* 11g R2 -* 12c -* 12c R2 -* 18c -* 19c +- Oracle Database 19c or newer (SE/EE/Cloud/Free) -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) +We do our best to assure full compatibility with **actively supported** versions of Oracle databases. +See [this page](https://www.oracle.com/us/support/library/lifetime-support-technology-069183.pdf#page=6) for Oracle Database Releases and their Support Lifetime by Oracle. -# 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 Edition of Oracle Database. -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 +29,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 +42,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 +66,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 +95,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 +106,17 @@ 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.
+ `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 +132,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 +148,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. @@ -186,13 +166,13 @@ If the installing user and utPLSQL owner is one and the same, the user must have In addition, the user must be granted the execute privilege on `DBMS_LOCK` and `DBMS_CRYPTO` packages. -utPLSQL is using [DBMS_PROFILER tables](https://docs.oracle.com/cd/E18283_01/appdev.112/e16760/d_profil.htm#i999476) for code coverage. The tables required by DBMS_PROFILER will be created in the installation schema unless they already exist. +utPLSQL is using [DBMS_PROFILER tables](https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_PROFILER.html) for code coverage. The tables required by DBMS_PROFILER will be created in the installation schema unless they already exist. 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 +185,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 +196,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 +211,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,29 +244,29 @@ 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). +This is a requirement of [DBMS_PROFILER package](https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_PROFILER.html#GUID-DEE5EA97-14AA-4AF2-A5F7-4AB1D004B99F). 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 +288,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..99ff5f044 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.2.4.4532--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..92c62cf72 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.2.4.4532--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,26 @@ Details: ![tfs_junit_reporter_outputs](../images/tfs_details.png) -# Coverage reporters +## 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. + +## TAP Reporter + +The `ut_tap_reporter` produces output compatible with the [Test Anything Protocol](https://testanything.org) (Version 14). TAP output can be consumed by a TAP consumer that can aggregate results from testsuites across different programming languages while maintaining good readability for humans. -utPLSQL comes with a set of build-in coverage reporters. Have a look into the [coverage documentation](coverage.md) to learn more about them. +![tap_output_no_color](../images/tap_reporter_no_color.png) +If you use a compatible terminal, you can also have a colored result. Only top level `not ok`-results will be colored: -# Debug reporter +![tap_colored_output](../images/tap_reporter_colored.png) + +Suites (and suitepaths) and contexts are included as [commented subtests](https://testanything.org/tap-version-14-specification.html#:~:text=Commented%20Subtests) including a summary of the tests performed for that specific context/suite. This example package has the suitepath "org.utplsql.tests.helpers": + +![tap_include_suitepath](../images/tap_reporter_suitepath.png) + +## Debug reporter The `ut_debug_reporter` provides a highly verbose output containing thorough details about framework and test execution. @@ -131,6 +149,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 +171,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..79671247e 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.2.4.4532--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..db02d6c50 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.2.4.4532--develop-blue.svg) # Upgrading from version 2 diff --git a/examples/RunAllExamples.sql b/examples/RunAllExamples.sql index faa3388cf..f5f42b7f3 100644 --- a/examples/RunAllExamples.sql +++ b/examples/RunAllExamples.sql @@ -3,22 +3,8 @@ set echo on set feedback on set linesize 1000 -declare - l_run_number binary_integer; -begin - dbms_profiler.start_profiler( run_number => l_run_number); -end; -/ - -- Examples for users @@RunUserExamples.sql -- Framework developer examples @@RunDeveloperExamples.sql - -declare - l_return_code binary_integer; -begin - l_return_code := dbms_profiler.stop_profiler(); -end; -/ diff --git a/examples/annoation_parser_testing/annotation_parser_test.sql b/examples/annoation_parser_testing/annotation_parser_test.sql new file mode 100644 index 000000000..d1ff43fcd --- /dev/null +++ b/examples/annoation_parser_testing/annotation_parser_test.sql @@ -0,0 +1,298 @@ +-- Annotation Manager Volume Benchmark +-- 1000 generated packages, cold cache per iteration + +set serveroutput on size unlimited +set timing off +set feedback off + +declare + c_packages constant pls_integer := 1000; + c_procs_per_pkg constant pls_integer := 20; + c_contexts_per_pkg constant pls_integer := 4; + c_pkg_prefix constant varchar2(20) := 'TST_GEN_PKG_'; + c_iterations constant pls_integer := 4; + c_perf_threshold constant number := 0.80; + + l_baseline_total_ms number := 0; + l_new_total_ms number := 0; + l_start timestamp; + l_elapsed interval day to second; + + -- Source holders + l_source_lines dbms_preprocessor.source_lines_t; + l_source_clob clob; + l_result ut_annotations; + + -- Correctness tracking + l_missing_from_cache pls_integer := 0; + l_count_mismatches pls_integer := 0; + l_mismatch_detail varchar2(32767); + + -- Cache check + l_cursor sys_refcursor; + l_annotated_obj ut_annotated_object; + l_name varchar2(128); + l_annotations ut_annotations; + l_cache_count pls_integer := 0; + + -- Per-package timing + l_pkg_baseline_ms number; + l_pkg_new_ms number; + l_pkg varchar2(30); + l_ratio number; + + -- Expected annotation count — mirrors generator logic exactly + function expected_annotation_count( + a_pkg_number pls_integer, + a_procs pls_integer, + a_contexts pls_integer + ) return pls_integer is + l_count pls_integer := 0; + begin + l_count := l_count + 2; -- %suite + %suitepath + l_count := l_count + 2; -- %beforeall + %afterall + l_count := l_count + 2; -- %beforeeach + %aftereach + + -- package level conditional + if mod(a_pkg_number, 3) = 0 then l_count := l_count + 1; end if; -- %displayname + if mod(a_pkg_number, 5) = 0 then l_count := l_count + 1; end if; -- %rollback + + -- contexts: each contributes %context + %endcontext + l_count := l_count + (a_contexts * 2); + + -- per procedure + for i in 1 .. a_procs loop + l_count := l_count + 1; -- %test + if mod(i, 3) = 0 then l_count := l_count + 1; end if; -- %tags + if mod(i, 6) = 0 then l_count := l_count + 1; end if; -- %throws + if mod(i, 8) = 0 then l_count := l_count + 1; end if; -- %disabled + if mod(i, 5) = 0 then l_count := l_count + 2; end if; -- %beforetest + %aftertest + end loop; + + return l_count; + end expected_annotation_count; + +begin + dbms_output.put_line('Packages : ' || c_packages); + dbms_output.put_line('Procs/pkg : ' || c_procs_per_pkg); + dbms_output.put_line('Contexts/pkg: ' || c_contexts_per_pkg); + dbms_output.put_line('Iterations : ' || c_iterations); + dbms_output.put_line('Started : ' || to_char(systimestamp,'YYYY-MM-DD HH24:MI:SS.FF3')); + + -- Phase 1: Performance benchmark (cold cache per package per iteration) + dbms_output.put_line(chr(10) || '-- Phase 1: Performance --'); + dbms_output.put_line( + rpad('package', 20) || ' | ' || + lpad('baseline_ms', 11) || ' | ' || + lpad('new_ms', 6) || ' | ' || + lpad('ratio', 5) || ' | ' || + 'status' + ); + dbms_output.put_line(rpad('-', 70, '-')); + + for p in 1 .. c_packages loop + l_pkg := c_pkg_prefix || lpad(p, 4, '0'); + + -- load source once per package + l_source_lines := dbms_preprocessor.get_post_processed_source( + object_type => 'PACKAGE', + schema_name => user, + object_name => l_pkg + ); + + dbms_lob.createtemporary(l_source_clob, true); + for i in 1 .. l_source_lines.count loop + dbms_lob.writeappend(l_source_clob, length(l_source_lines(i)), l_source_lines(i)); + end loop; + + -- baseline: clob version + l_pkg_baseline_ms := 0; + for iter in 1 .. c_iterations loop + ut_annotation_manager.purge_cache(user, 'PACKAGE'); + l_start := systimestamp; + l_result := ut_annotation_parser.parse_object_annotations(l_source_clob); + l_elapsed := systimestamp - l_start; + l_pkg_baseline_ms := l_pkg_baseline_ms + + extract(second from l_elapsed) * 1000; + end loop; + l_pkg_baseline_ms := l_pkg_baseline_ms / c_iterations; + + -- new: source_lines_t version + l_pkg_new_ms := 0; + for iter in 1 .. c_iterations loop + ut_annotation_manager.purge_cache(user, 'PACKAGE'); + l_start := systimestamp; + l_result := ut_annotation_parser.parse_object_annotations(l_source_lines); + l_elapsed := systimestamp - l_start; + l_pkg_new_ms := l_pkg_new_ms + + extract(second from l_elapsed) * 1000; + end loop; + l_pkg_new_ms := l_pkg_new_ms / c_iterations; + + -- accumulate + l_baseline_total_ms := l_baseline_total_ms + l_pkg_baseline_ms; + l_new_total_ms := l_new_total_ms + l_pkg_new_ms; + + l_ratio := round(l_pkg_new_ms / nullif(l_pkg_baseline_ms, 0), 3); + + -- print every 100 packages to avoid flooding output + if mod(p, 100) = 0 or p = 1 then + dbms_output.put_line( + rpad(l_pkg, 20) || ' | ' || + lpad(round(l_pkg_baseline_ms, 3), 11) || ' | ' || + lpad(round(l_pkg_new_ms, 3), 6) || ' | ' || + lpad(l_ratio, 5) || ' | ' || + case when l_ratio <= c_perf_threshold + then 'PASS (' || round((1-l_ratio)*100,1) || '% faster)' + else 'SLOW (ratio=' || l_ratio || ')' + end + ); + end if; + + dbms_lob.freetemporary(l_source_clob); + end loop; + + -- ------------------------------------------------------------------------- + -- Phase 1 summary + -- ------------------------------------------------------------------------- + l_ratio := round(l_new_total_ms / nullif(l_baseline_total_ms, 0), 3); + + dbms_output.put_line(rpad('-', 70, '-')); + dbms_output.put_line('Baseline total ms : ' || round(l_baseline_total_ms, 3)); + dbms_output.put_line('New total ms : ' || round(l_new_total_ms, 3)); + dbms_output.put_line('Overall ratio : ' || l_ratio); + dbms_output.put_line('Improvement : ' || round((1 - l_ratio) * 100, 1) || '%'); + + -- Phase 2: Cache presence check + dbms_output.put_line(chr(10) || '-- Phase 2: Cache presence --'); + + ut_annotation_manager.purge_cache(user, 'PACKAGE'); + ut_annotation_manager.rebuild_annotation_cache(user, 'PACKAGE'); + + -- query cache tables directly to avoid validate_annotation_cache side effects + open l_cursor for + select ut_annotated_object( + i.object_owner, i.object_name, i.object_type, i.parse_time, + cast( + collect( + ut_annotation( + c.annotation_position, c.annotation_name, + c.annotation_text, c.subobject_name + ) order by c.annotation_position + ) as ut_annotations + ) + ) + from ut_annotation_cache_info i + join ut_annotation_cache c on i.cache_id = c.cache_id + where i.object_owner = user + and i.object_type = 'PACKAGE' + and i.object_name like c_pkg_prefix || '%' + group by i.object_owner, i.object_type, i.object_name, i.parse_time; + + loop + fetch l_cursor into l_annotated_obj; + exit when l_cursor%notfound; + l_cache_count := l_cache_count + 1; + end loop; + close l_cursor; + + dbms_output.put_line('Expected in cache : ' || c_packages); + dbms_output.put_line('Found in cache : ' || l_cache_count); + dbms_output.put_line('Cache check : ' || + case when l_cache_count = c_packages + then 'PASS — all ' || c_packages || ' packages found in cache' + else 'FAIL — missing ' || (c_packages - l_cache_count) || ' packages from cache' + end + ); + + -- Phase 3: Annotation count correctness + dbms_output.put_line(chr(10) || '-- Phase 3: Annotation count correctness --'); + + -- reopen same direct cache query for correctness check + open l_cursor for + select ut_annotated_object( + i.object_owner, i.object_name, i.object_type, i.parse_time, + cast( + collect( + ut_annotation( + c.annotation_position, c.annotation_name, + c.annotation_text, c.subobject_name + ) order by c.annotation_position + ) as ut_annotations + ) + ) + from ut_annotation_cache_info i + join ut_annotation_cache c on i.cache_id = c.cache_id + where i.object_owner = user + and i.object_type = 'PACKAGE' + and i.object_name like c_pkg_prefix || '%' + group by i.object_owner, i.object_type, i.object_name, i.parse_time; + + loop + fetch l_cursor into l_annotated_obj; + exit when l_cursor%notfound; + l_name := l_annotated_obj.object_name; + l_annotations := l_annotated_obj.annotations; + + declare + l_pkg_number pls_integer; + l_expected pls_integer; + begin + l_pkg_number := to_number(substr(l_name, length(c_pkg_prefix) + 1)); + l_expected := expected_annotation_count( + l_pkg_number, + c_procs_per_pkg, + c_contexts_per_pkg + ); + + if l_annotations.count != l_expected then + l_count_mismatches := l_count_mismatches + 1; + if l_count_mismatches <= 10 then + l_mismatch_detail := l_mismatch_detail || chr(10) || + ' ' || l_name || + ': expected=' || l_expected || + ' actual=' || l_annotations.count; + end if; + end if; + end; + end loop; + close l_cursor; + + dbms_output.put_line('Packages checked : ' || c_packages); + dbms_output.put_line('Count mismatches : ' || l_count_mismatches); + if l_count_mismatches > 0 then + dbms_output.put_line('Mismatch detail (first 10):' || l_mismatch_detail); + end if; + dbms_output.put_line('Count check : ' || + case when l_count_mismatches = 0 + then 'PASS — all annotation counts match expected' + else 'FAIL — ' || l_count_mismatches || ' packages have wrong annotation count' + end + ); + + dbms_output.put_line(chr(10) || '================================================='); + dbms_output.put_line('FINAL RESULTS'); + dbms_output.put_line('================================================='); + dbms_output.put_line('Performance : ' || + case when l_ratio <= c_perf_threshold then 'PASS' else 'FAIL' end); + dbms_output.put_line('Cache : ' || + case when l_cache_count = c_packages then 'PASS' else 'FAIL' end); + dbms_output.put_line('Counts : ' || + case when l_count_mismatches = 0 then 'PASS' else 'FAIL' end); + dbms_output.put_line('Overall : ' || + case when l_ratio <= c_perf_threshold + and l_cache_count = c_packages + and l_count_mismatches = 0 + then 'PASS' + else 'FAIL' + end + ); + dbms_output.put_line('Finished : ' || + to_char(systimestamp, 'YYYY-MM-DD HH24:MI:SS.FF3')); + dbms_output.put_line('================================================='); + + -- cleanup + ut_annotation_manager.purge_cache(user, 'PACKAGE'); + +end; +/ diff --git a/examples/annoation_parser_testing/create_test_packages.sql b/examples/annoation_parser_testing/create_test_packages.sql new file mode 100644 index 000000000..94a7f3de2 --- /dev/null +++ b/examples/annoation_parser_testing/create_test_packages.sql @@ -0,0 +1,185 @@ +declare + c_packages constant pls_integer := 1000; + c_procs_per_pkg constant pls_integer := 20; + + l_spec varchar2(32767); + l_body varchar2(32767); + l_pkg varchar2(30); + l_errors pls_integer := 0; + + procedure append_to_source(a_source in out nocopy varchar2, a_text varchar2) is + begin + a_source := a_source || a_text; + end; + + procedure append_line(a_source in out nocopy varchar2, a_text varchar2 default null) is + begin + a_source := a_source || a_text || chr(10); + end; + +begin + + for r in ( + select object_name + from user_objects + where object_name like 'TST_GEN_PKG_%' + and object_type = 'PACKAGE' + order by object_name + ) loop + begin + execute immediate 'drop package ' || r.object_name; + exception + when others then + dbms_output.put_line('ERROR dropping ' || r.object_name || ': ' || sqlerrm); + end; + end loop; + dbms_output.put_line('Existing packages dropped.'); + + for p in 1 .. c_packages loop + l_pkg := 'TST_GEN_PKG_' || lpad(p, 4, '0'); + l_spec := null; + l_body := null; + + -- Package spec + append_line(l_spec, 'create or replace package ' || l_pkg || ' as'); + append_line(l_spec); + append_line(l_spec, ' --%suite(Generated suite ' || p || ')'); + append_line(l_spec, ' --%suitepath(generated.vol_test)'); + + if mod(p, 3) = 0 then + append_line(l_spec, ' --%displayname(Suite display name for pkg ' || p || ')'); + end if; + if mod(p, 5) = 0 then + append_line(l_spec, ' --%rollback(manual)'); + end if; + + append_line(l_spec); + append_line(l_spec, ' --%beforeall'); + append_line(l_spec, ' procedure setup_suite;'); + append_line(l_spec); + append_line(l_spec, ' --%afterall'); + append_line(l_spec, ' procedure teardown_suite;'); + append_line(l_spec); + append_line(l_spec, ' --%beforeeach'); + append_line(l_spec, ' procedure setup_test;'); + append_line(l_spec); + append_line(l_spec, ' --%aftereach'); + append_line(l_spec, ' procedure teardown_test;'); + append_line(l_spec); + + for i in 1 .. c_procs_per_pkg loop + declare + l_proc varchar2(30) := 'test_proc_' || lpad(i, 3, '0'); + begin + if mod(i - 1, 5) = 0 then + append_line(l_spec, ' --%context(context_' || ceil(i/5) || ')'); + append_line(l_spec); + end if; + + if mod(i, 4) = 0 then + append_line(l_spec, ' /* multi-line comment for ' || l_proc || ' */'); + end if; + + append_line(l_spec, ' --%test(Test ' || i || ' in pkg ' || p || ')'); + + if mod(i, 3) = 0 then + append_line(l_spec, ' --%tags(tag_' || mod(i,5) || ',smoke)'); + end if; + if mod(i, 6) = 0 then + append_line(l_spec, ' --%throws(-20001)'); + end if; + if mod(i, 8) = 0 then + append_line(l_spec, ' --%disabled(generated disabled test ' || i || ')'); + end if; + if mod(i, 5) = 0 then + append_line(l_spec, ' --%beforetest(setup_test)'); + append_line(l_spec, ' --%aftertest(teardown_test)'); + end if; + + append_line(l_spec, ' -- regular comment'); + append_line(l_spec, ' procedure ' || l_proc || ';'); + append_line(l_spec); + + if mod(i, 5) = 0 then + append_line(l_spec, ' --%endcontext'); + append_line(l_spec); + end if; + end; + end loop; + + append_line(l_spec, 'end ' || l_pkg || ';'); + + -- ----------------------------------------------------------------------- + -- Package body + -- ----------------------------------------------------------------------- + append_line(l_body, 'create or replace package body ' || l_pkg || ' as'); + append_line(l_body); + + for stub in ( + select column_value as proc_name + from table(ut_varchar2_list( + 'setup_suite','teardown_suite', + 'setup_test','teardown_test' + )) + ) loop + append_line(l_body, ' procedure ' || stub.proc_name || ' is'); + append_line(l_body, ' begin'); + append_line(l_body, ' null;'); + append_line(l_body, ' end ' || stub.proc_name || ';'); + append_line(l_body); + end loop; + + for i in 1 .. c_procs_per_pkg loop + declare + l_proc varchar2(30) := 'test_proc_' || lpad(i, 3, '0'); + begin + append_line(l_body, ' procedure ' || l_proc || ' is'); + append_line(l_body, ' begin'); + if mod(i, 4) = 0 then + append_line(l_body, ' ut.expect(''/* not a comment */'').to_equal(''/* not a comment */'');'); + elsif mod(i, 4) = 1 then + append_line(l_body, ' ut.expect(' || i || ').to_be_greater_than(0);'); + elsif mod(i, 4) = 2 then + append_line(l_body, ' ut.expect(q''[-- not a comment]'').to_equal(q''[-- not a comment]'');'); + else + append_line(l_body, ' ut.expect(''test_' || i || ''').not_to_be_null();'); + end if; + append_line(l_body, ' end ' || l_proc || ';'); + append_line(l_body); + end; + end loop; + + append_line(l_body, 'end ' || l_pkg || ';'); + + -- Execute DDL — with error capture so one bad package doesn't stop all + begin + execute immediate l_spec; + execute immediate l_body; + exception + when others then + l_errors := l_errors + 1; + dbms_output.put_line('ERROR on ' || l_pkg || ': ' || sqlerrm); + end; + + if mod(p, 100) = 0 then + dbms_output.put_line('Created ' || p || ' packages...'); + end if; + + end loop; + + dbms_output.put_line('----------------------------------------'); + dbms_output.put_line('Done. packages=' || c_packages || + ' errors=' || l_errors); + + -- quick sanity check + declare + l_count pls_integer; + begin + select count(*) into l_count + from user_objects + where object_name like 'TST_GEN_PKG_%' + and object_type = 'PACKAGE'; + dbms_output.put_line('Packages in user_objects: ' || l_count); + end; +end; +/ 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..f18f74ce3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,17 +1,127 @@ # 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: https://www.utplsql.org/ site_name: utPLSQL -site_description: utPLSQL Ultimate Unit Testing Framework for Oracle PL/SQL -copyright: Copyright © 2016 - 2018 utPLSQL Team +site_description: utPLSQL Ultimate Testing Framework for Oracle PL/SQL +copyright: > + Copyright © 2016 - 2026 utPLSQL Team + Change cookie settings repo_url: https://github.com/utPLSQL/utPLSQL -theme: mkdocs +extra_css: + - stylesheets/extra.css +theme: + name: material + custom_dir: docs/overrides + 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 + - navigation.top + - toc.follow + - toc.integrate + - search.suggest + - search.highlight + - content.code.copy + - content.code.annotate + - content.code.select + +extra: +# homepage: https://www.utPLSQL.org/ + # sets scope of cookies to use a common path (root) + scope: "/" + analytics: + provider: google + property: G-WMFVR98J0Q + feedback: + title: Was this page helpful? + ratings: + - icon: material/emoticon-happy-outline + name: This page was helpful + data: 1 + note: >- + Thanks for your feedback! + - icon: material/emoticon-sad-outline + name: This page could be improved + data: 0 + note: >- + Thanks for your feedback! Help us improve this page by + using our GitHub Discussions. + 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. + # the cookie: utplsql-consent allows for shred cookie across all documentation versions and the global site + cookie: utplsql-consent + social: + - icon: fontawesome/brands/twitter + link: https://twitter.com/utPLSQL + - icon: fontawesome/regular/comment + link: https://github.com/utPLSQL/utPLSQL/discussions + - icon: fontawesome/brands/github + link: https://github.com/utPLSQL + - icon: fontawesome/solid/envelope + link: mailto:utPLSQL@utPLSQL.org + version: # disable for offline docs + provider: mike # disable for offline docs +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.caret + - pymdownx.mark + - pymdownx.tilde + - toc: + permalink: true use_directory_urls: false strict: true +plugins: + - search + - git-committers: + repository: utPLSQL/utPLSQL + branch: develop + token: !ENV ["API_TOKEN_GITHUB"] + exclude_committers: + - github-actions[bot] + - web-flow + - 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,14 +129,9 @@ 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 - - About: - - Project Details: about/project-details.md - - License: about/license.md - - Support: about/support.md - - Authors: about/authors.md + - Error handling and reporting: userguide/exception-reporting.md diff --git a/mkdocs_offline.yml b/mkdocs_offline.yml new file mode 100644 index 000000000..82304041c --- /dev/null +++ b/mkdocs_offline.yml @@ -0,0 +1,99 @@ +# 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: https://www.utplsql.org/ +site_name: utPLSQL-framework +site_description: utPLSQL Ultimate Testing Framework for Oracle PL/SQL & SQL +copyright: Copyright © 2016 - 2026 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: https://www.utPLSQL.org/ + social: + - icon: fontawesome/brands/twitter + link: https://twitter.com/utPLSQL + - icon: fontawesome/regular/comment + link: https://github.com/utPLSQL/utPLSQL/discussions + - 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 diff --git a/readme.md b/readme.md index 141c68ffe..ac4dc9815 100644 --- a/readme.md +++ b/readme.md @@ -1,102 +1,47 @@ ![utPLSQL v3 | Testing Framework for PL/SQL](docs/images/utPLSQL-testing-framework-transparent_120.png) ----------- - -[![license](https://img.shields.io/github/license/utPLSQL/utPLSQL.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![latest-release](https://img.shields.io/github/release/utPLSQL/utPLSQL.svg)](https://github.com/utPLSQL/utPLSQL/releases) -[![Download statistics](https://img.shields.io/github/downloads/utPLSQL/utPLSQL/total.svg)](http://gra.caldis.me/?url=https://github.com/utPLSQL/utPLSQL) -[![chat](http://img.shields.io/badge/slack-team--chat-blue.svg)](https://join.slack.com/t/utplsql/shared_invite/zt-xwm68udy-4cF_3PNEyczYEbWr38W5ww) -[![twitter](https://img.shields.io/twitter/follow/utPLSQL.svg?style=social&label=Follow)](https://twitter.com/utPLSQL) - +[![license](https://img.shields.io/github/license/utPLSQL/utPLSQL.svg)](https://www.apache.org/licenses/LICENSE-2.0) +[![GitHub Discussions](https://img.shields.io/github/discussions/utPLSQL/utPLSQL)](https://github.com/utPLSQL/utPLSQL/discussions) +[![X](https://img.shields.io/twitter/follow/utPLSQL.svg?style=social&label=Follow)](https://twitter.com/utPLSQL) [![build](https://github.com/utPLSQL/utPLSQL/actions/workflows/build.yml/badge.svg)](https://github.com/utPLSQL/utPLSQL/actions/workflows/build.yml) -[![QualityGate](https://sonarcloud.io/api/project_badges/measure?project=utPLSQL&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=utPLSQL) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=utPLSQL&metric=coverage)](https://sonarcloud.io/summary/new_code?id=utPLSQL) - ----------- -utPLSQL version 3 is a complete rewrite of utPLSQL v2 from scratch. -Version 2 still supports older versions of Oracle that are no longer available. -The community that had developed on GitHub decided that a new internal architecture was needed, from that version 3 was born. - -# Introduction -utPLSQL is a Unit Testing framework for Oracle PL/SQL and 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/) - - -# Key features - -- multiple ways to compare data with [matchers](docs/userguide/expectations.md) -- native comparison of complex types (objects/collections/cursors) -- in-depth and consistent reporting of failures and errors for tests -- tests identified and configured by [annotations](docs/userguide/annotations.md) -- hierarchies of test suites configured with annotations -- automatic (configurable) transaction control -- Build-in [coverage](docs/userguide/coverage.md) reporting -- Integration with SonarQube, Coveralls, Jenkins and Teamcity with [reporters](docs/userguide/reporters.md) -- plugin architecture for reporters and matchers -- flexible and simple test invocation -- multi-reporting from test-run from [command line](https://github.com/utPLSQL/utPLSQL-cli) - -Requirements: - - Version of Oracle under [extended support](http://www.oracle.com/us/support/library/lifetime-support-technology-069183.pdf) (Currently 11.2 and above) - -# Download - -Published releases are available for download on the [utPLSQL GitHub Releases Page.](https://github.com/utPLSQL/utPLSQL/releases) - -# Documentation - -Full documentation of the project is automatically published on [utPLSQL github pages](https://utplsql.github.io/utPLSQL/) +[![QualityGate](https://sonarcloud.io/api/project_badges/measure?project=utPLSQL_utPLSQL&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=utPLSQL_utPLSQL) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=utPLSQL_utPLSQL&metric=coverage)](https://sonarcloud.io/summary/new_code?id=utPLSQL_utPLSQL) -[Cheat-sheets](https://www.cheatography.com/jgebal/lists/utplsql-v3-cheat-sheets/) +utPLSQL is a unit testing framework for Oracle PL/SQL and SQL, following industry standards and best practices of modern testing frameworks like [JUnit](http://junit.org/junit4/) and [RSpec](http://rspec.info/). -# Installation - -To install the utPLSQL into a new database schema and grant it to public, execute the script `install_headless.sql`. -This will create a new user `UT3`, grant all required privileges to that user and create PUBLIC synonyms needed. - -For detailed instructions on other install options see the [Install Guide](docs/userguide/install.md) - - -# Running tests - -To execute using development IDE (TOAD/SQLDeveloper/PLSQLDeveloper/other) use one of following commands. -```sql -begin - ut.run(); -end; -/ -``` -```sql -exec ut.run(); -``` -```sql -select * from table(ut.run()); -``` +## Key Features -The above commands will run all the suites in the current schema and provide report to dbms_output or as a select statement. +- Multiple ways to compare data with [matchers](docs/userguide/expectations.md) +- Native comparison of complex types (objects, collections, cursors) +- In-depth and consistent reporting of failures and errors +- Tests identified and configured by [annotations](docs/userguide/annotations.md) +- Hierarchies of test suites configured with annotations +- Automatic (configurable) transaction control +- Built-in [coverage](docs/userguide/coverage.md) reporting +- Integration with SonarQube, Jenkins and TeamCity via [reporters](docs/userguide/reporters.md) +- Plugin architecture for reporters and matchers +- Flexible and simple test invocation +- Multi-format reporting from the [command line client](https://github.com/utPLSQL/utPLSQL-cli) -# Command line client +## Requirements -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. +- Oracle Database 19c or newer -Amongst many benefits it provides 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 +## Installation -Download the [latest client](https://github.com/utPLSQL/utPLSQL-cli/releases/latest) and are good to go. -See [project readme](https://github.com/utPLSQL/utPLSQL-cli/blob/develop/README.md) for details. +Published releases are available for download on the [utPLSQL GitHub Releases page](https://github.com/utPLSQL/utPLSQL/releases). +To install utPLSQL into a new database schema and grant it to public, execute the script `install_headless.sql`. +This creates a new user `UT3`, grants all required privileges, and creates PUBLIC synonyms. -# Example unit test packages +For all install options see the [Install Guide](docs/userguide/install.md). -**For examples of using Continuous Integration Server & SonarCloud with utPLSQL see the [utPLSQL demo project](https://github.com/utPLSQL/utPLSQL-demo-project/).** +## Quick Start +**1. Write a test package** -The below test package is a fully-functional Unit Test package for testing a [`betwnstr` function](examples/between_string/betwnstr.sql). -The package specification is [annotated](docs/userguide/annotations.md) with special comments. -The annotations define that a package is a unit test suite, they also allow defining a description for the suite as well as the test itself. -The package body consists of procedures containing unit test code. To validate [an expectation](docs/userguide/expectations.md) in test, use `ut.expect( actual_data ).to_( ... )` syntax. +Annotate a package specification to define a test suite and its tests, then implement each test procedure in the package body: ```sql create or replace package test_between_string as @@ -106,12 +51,6 @@ create or replace package test_between_string as -- %test(Returns substring from start position to end position) procedure normal_case; - -- %test(Returns substring when start position is zero) - procedure zero_start_position; - - -- %test(Returns string until end if end position is greater than string length) - procedure big_end_position; - -- %test(Returns null for null input string value) procedure null_string; end; @@ -124,16 +63,6 @@ create or replace package body test_between_string as ut.expect( betwnstr( '1234567', 2, 5 ) ).to_( equal('2345') ); end; - procedure zero_start_position is - begin - ut.expect( betwnstr( '1234567', 0, 5 ) ).to_( equal('12345') ); - end; - - procedure big_end_position is - begin - ut.expect( betwnstr( '1234567', 0, 500 ) ).to_( equal('1234567') ); - end; - procedure null_string is begin ut.expect( betwnstr( null, 2, 5 ) ).to_( be_null ); @@ -143,63 +72,69 @@ end; / ``` -Outputs from running the above tests +**2. Run your tests** + +```sql +exec ut.run(); +``` + +**3. See the results** + ``` Between string function Returns substring from start position to end position - Returns substring when start position is zero - Returns string until end if end position is greater than string length Returns null for null input string value Finished in .036027 seconds -4 tests, 0 failures +2 tests, 0 failures ``` +For complete working examples see [examples/](examples/). + +## Running Tests -# Contributing to the project +From any Oracle-compatible IDE (SQL Developer, TOAD, PL/SQL Developer): + +```sql +begin + ut.run(); +end; +/ +``` +```sql +exec ut.run(); +``` +```sql +select * from table(ut.run()); +``` -We welcome new developers to join our community and contribute to the utPLSQL project. -If you are interested in helping please read our [guide to contributing](CONTRIBUTING.md) -The best place to start is to read the documentation and get familiar with the existing code base. -A [slack chat](https://utplsql.slack.com/) is the place to go if you want to talk with team members. -To sign up to the chat use [this link](https://join.slack.com/t/utplsql/shared_invite/zt-xwm68udy-4cF_3PNEyczYEbWr38W5ww). +These commands run all suites in the current schema and report test results to `DBMS_OUTPUT` or as a result set. +## Command Line Client ----------- -[__Authors__](docs/about/authors.md) +[utPLSQL-cli](https://github.com/utPLSQL/utPLSQL-cli) lets you run tests without an Oracle Client or IDE. It provides: +- Real-time test reporting +- Simultaneous output of multiple report formats into different files +- Source files and test files mapping for coverage reports ----------- -__Project Directories__ +Download the [latest release](https://github.com/utPLSQL/utPLSQL-cli/releases/latest) and see the [CLI readme](https://github.com/utPLSQL/utPLSQL-cli/blob/develop/README.md) for details. -* .github - contains files needed for github Actions integration -* .travis - contains files needed for travis-ci integration -* client_source - Sources to be used on the client-side. Developer workstation or CI platform to run the tests. -* development - Set of useful scripts and utilities for development and debugging of utPLSQL -* docs - Documentation of the project -* examples - Example source code and unit tests -* source - The installation code for utPLSQL -* tests - Tests for utPLSQL framework +## Documentation ----------- +Full documentation is published at [https://www.utplsql.org/utPLSQL/](https://www.utplsql.org/utPLSQL/). -If you have a great feature in mind, that you would like to see in utPLSQL v3 please create an [issue on GitHub](https://github.com/utPLSQL/utPLSQL/issues) or discuss it with us in the [slack chat rooms](https://utplsql.slack.com/). Use [invite link](https://join.slack.com/t/utplsql/shared_invite/zt-xwm68udy-4cF_3PNEyczYEbWr38W5ww) to join the chat. +[Cheat-sheets](https://www.cheatography.com/jgebal/lists/utplsql-v3-cheat-sheets/) are available for quick reference. +See the [Changelog](CHANGELOG.md) for the full version history. -# Version 2 to Version 3 Comparison +Migrating from legacy utPLSQL v2? See the [version 2 to version 3 comparison](docs/compare_version2_to_3.md). -[Version 2 to Version 3 Comparison](docs/compare_version2_to_3.md) +## Contributing -# Supporters +We welcome contributions of all kinds. Please read the [contributing guide](CONTRIBUTING.md) and the [code of conduct](https://github.com/utPLSQL/utPLSQL?tab=coc-ov-file) before getting started. -The utPLSQL project is community-driven and is not commercially motivated. Nonetheless, donations and other contributions are always welcome, and are detailed below. +[GitHub Discussions](https://github.com/utPLSQL/utPLSQL/discussions) is the place to ask questions and connect with the team. - - - - - - - -
supported_by_redgateutPLSQL has been supported by Redgate in the form of sponsored stickers and t-shirts. Thank you for helping us spreading the word!
+The list of **authors** and **significant contributors** is available on the [About](https://www.utplsql.org/about/) page. For a full list of contributors, see [contributors](https://github.com/utPLSQL/utPLSQL/graphs/contributors?all=1) on GitHub. diff --git a/sonar-project.properties b/sonar-project.properties index 5d6cefe12..0a835a2f3 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,9 +1,9 @@ # must be unique in a given SonarQube instance sonar.organization=utplsql -sonar.projectKey=utPLSQL +sonar.projectKey=utPLSQL_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.2.4-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. @@ -15,7 +15,7 @@ sonar.coverage.exclusions=**/*.sql,**/*.pks sonar.tests=./test sonar.testExecutionReportPaths=./test_results.xml sonar.links.issue=https://github.com/utPLSQL/utPLSQL/issues -sonar.links.ci=https://travis-ci.org/utPLSQL/utPLSQL +sonar.links.ci=https://github.com/utPLSQL/utPLSQL/actions sonar.links.homepage=https://github.com/utPLSQL/utPLSQL sonar.projectDescription=PL/SQL Unit Testing Framework sonar.plsql.file.suffixes=sql,tab,pkb,tpb diff --git a/source/api/be_between.syn b/source/api/be_between.syn index c0972595d..233dd65bf 100644 --- a/source/api/be_between.syn +++ b/source/api/be_between.syn @@ -1 +1 @@ -create synonym be_between for ut_be_between; +create noneditionable synonym be_between for ut_be_between; diff --git a/source/api/be_empty.syn b/source/api/be_empty.syn index a8e28de6e..f63366fa3 100644 --- a/source/api/be_empty.syn +++ b/source/api/be_empty.syn @@ -1 +1 @@ -create synonym be_empty for ut_be_empty; +create noneditionable synonym be_empty for ut_be_empty; diff --git a/source/api/be_false.syn b/source/api/be_false.syn index c2665bbcb..cdf497235 100644 --- a/source/api/be_false.syn +++ b/source/api/be_false.syn @@ -1 +1 @@ -create synonym be_false for ut_be_false; +create noneditionable synonym be_false for ut_be_false; diff --git a/source/api/be_greater_or_equal.syn b/source/api/be_greater_or_equal.syn index 386fe7f32..67616dc1d 100644 --- a/source/api/be_greater_or_equal.syn +++ b/source/api/be_greater_or_equal.syn @@ -1 +1 @@ -create synonym be_greater_or_equal for ut_be_greater_or_equal; +create noneditionable synonym be_greater_or_equal for ut_be_greater_or_equal; diff --git a/source/api/be_greater_than.syn b/source/api/be_greater_than.syn index 6f97b7d22..c180f3d43 100644 --- a/source/api/be_greater_than.syn +++ b/source/api/be_greater_than.syn @@ -1 +1 @@ -create synonym be_greater_than for ut_be_greater_than; +create noneditionable synonym be_greater_than for ut_be_greater_than; diff --git a/source/api/be_less_or_equal.syn b/source/api/be_less_or_equal.syn index b2f836144..0069f52ba 100644 --- a/source/api/be_less_or_equal.syn +++ b/source/api/be_less_or_equal.syn @@ -1 +1 @@ -create synonym be_less_or_equal for ut_be_less_or_equal; +create noneditionable synonym be_less_or_equal for ut_be_less_or_equal; diff --git a/source/api/be_less_than.syn b/source/api/be_less_than.syn index fbef3b4c3..5d2766d05 100644 --- a/source/api/be_less_than.syn +++ b/source/api/be_less_than.syn @@ -1 +1 @@ -create synonym be_less_than for ut_be_less_than; +create noneditionable synonym be_less_than for ut_be_less_than; diff --git a/source/api/be_like.syn b/source/api/be_like.syn index a8be1de71..6f62f21a0 100644 --- a/source/api/be_like.syn +++ b/source/api/be_like.syn @@ -1 +1 @@ -create synonym be_like for ut_be_like; +create noneditionable synonym be_like for ut_be_like; diff --git a/source/api/be_not_null.syn b/source/api/be_not_null.syn index d6458683a..a9e02b94d 100644 --- a/source/api/be_not_null.syn +++ b/source/api/be_not_null.syn @@ -1 +1 @@ -create synonym be_not_null for ut_be_not_null; +create noneditionable synonym be_not_null for ut_be_not_null; diff --git a/source/api/be_null.syn b/source/api/be_null.syn index 42f6516c3..4fe3fb9dd 100644 --- a/source/api/be_null.syn +++ b/source/api/be_null.syn @@ -1 +1 @@ -create synonym be_null for ut_be_null; +create noneditionable synonym be_null for ut_be_null; diff --git a/source/api/be_true.syn b/source/api/be_true.syn index bf7392a62..1aa348c7e 100644 --- a/source/api/be_true.syn +++ b/source/api/be_true.syn @@ -1 +1 @@ -create synonym be_true for ut_be_true; +create noneditionable synonym be_true for ut_be_true; diff --git a/source/api/be_within.syn b/source/api/be_within.syn index e00005bb7..4f4f77606 100644 --- a/source/api/be_within.syn +++ b/source/api/be_within.syn @@ -1 +1 @@ -create synonym be_within for ut_be_within; +create noneditionable synonym be_within for ut_be_within; diff --git a/source/api/be_within_pct.syn b/source/api/be_within_pct.syn index 40e3fb8b9..b9fc1cd53 100644 --- a/source/api/be_within_pct.syn +++ b/source/api/be_within_pct.syn @@ -1 +1 @@ -create synonym be_within_pct for ut_be_within_pct; +create noneditionable synonym be_within_pct for ut_be_within_pct; diff --git a/source/api/contain.syn b/source/api/contain.syn index 5bce7d1d2..5b4158635 100644 --- a/source/api/contain.syn +++ b/source/api/contain.syn @@ -1 +1 @@ -create synonym contain for ut_contain; +create noneditionable synonym contain for ut_contain; diff --git a/source/api/equal.syn b/source/api/equal.syn index 71ba58b53..0d81fcf6f 100644 --- a/source/api/equal.syn +++ b/source/api/equal.syn @@ -1 +1 @@ -create synonym equal for ut_equal; +create noneditionable synonym equal for ut_equal; diff --git a/source/api/have_count.syn b/source/api/have_count.syn index a406d47ec..9cbf8ebf2 100644 --- a/source/api/have_count.syn +++ b/source/api/have_count.syn @@ -1 +1 @@ -create synonym have_count for ut_have_count; +create noneditionable synonym have_count for ut_have_count; diff --git a/source/api/match.syn b/source/api/match.syn index ffbda35b1..13e8cc97d 100644 --- a/source/api/match.syn +++ b/source/api/match.syn @@ -1 +1 @@ -create synonym match for ut_match; +create noneditionable synonym match for ut_match; diff --git a/source/api/ut.pkb b/source/api/ut.pkb index 5c91e54d4..0a847cfaa 100644 --- a/source/api/ut.pkb +++ b/source/api/ut.pkb @@ -1,8 +1,8 @@ -create or replace package body ut is +create or replace noneditionable package body ut is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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.pks b/source/api/ut.pks index f72c82a0c..fdba6b019 100644 --- a/source/api/ut.pks +++ b/source/api/ut.pks @@ -1,8 +1,8 @@ -create or replace package ut authid current_user as +create or replace noneditionable package ut authid current_user as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index b69a51a04..0db1d6e96 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -1,8 +1,8 @@ -create or replace package body ut_runner is +create or replace noneditionable package body ut_runner is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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/api/ut_runner.pks b/source/api/ut_runner.pks index 85eff1c93..17bd0fa7a 100644 --- a/source/api/ut_runner.pks +++ b/source/api/ut_runner.pks @@ -1,8 +1,8 @@ -create or replace package ut_runner authid current_user is +create or replace noneditionable package ut_runner authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/api/ut_suite_item_info.tpb b/source/api/ut_suite_item_info.tpb index 3315f7a16..d18f078ee 100644 --- a/source/api/ut_suite_item_info.tpb +++ b/source/api/ut_suite_item_info.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_suite_item_info is +create or replace noneditionable type body ut_suite_item_info is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/api/ut_suite_item_info.tps b/source/api/ut_suite_item_info.tps index 2c92f261d..4b4f9d52c 100644 --- a/source/api/ut_suite_item_info.tps +++ b/source/api/ut_suite_item_info.tps @@ -1,7 +1,7 @@ -create or replace type ut_suite_item_info as object ( +create or replace noneditionable type ut_suite_item_info as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/api/ut_suite_items_info.tps b/source/api/ut_suite_items_info.tps index 208098f9d..5a04c9a74 100644 --- a/source/api/ut_suite_items_info.tps +++ b/source/api/ut_suite_items_info.tps @@ -1,7 +1,7 @@ -create or replace type ut_suite_items_info as +create or replace noneditionable type ut_suite_items_info as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/check_object_grants.sql b/source/check_object_grants.sql index 5f560e6b1..0e7a9b4c9 100644 --- a/source/check_object_grants.sql +++ b/source/check_object_grants.sql @@ -1,3 +1,20 @@ +/* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. +*/ + declare $if dbms_db_version.version >= 18 $then c_expected_grants constant dbmsoutput_linesarray := dbmsoutput_linesarray('DBMS_CRYPTO'); diff --git a/source/check_sys_grants.sql b/source/check_sys_grants.sql index 79b657328..f81157183 100644 --- a/source/check_sys_grants.sql +++ b/source/check_sys_grants.sql @@ -1,3 +1,20 @@ +/* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. +*/ + define expected_grants = "&1" declare c_expected_grants constant dbmsoutput_linesarray := dbmsoutput_linesarray( &expected_grants ); diff --git a/source/core/annotations/ut_annotated_object.tps b/source/core/annotations/ut_annotated_object.tps index 3fda363fe..ca327d7fc 100644 --- a/source/core/annotations/ut_annotated_object.tps +++ b/source/core/annotations/ut_annotated_object.tps @@ -1,7 +1,7 @@ -create type ut_annotated_object as object( +create or replace noneditionable type ut_annotated_object as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_annotated_objects.tps b/source/core/annotations/ut_annotated_objects.tps index c67c1bd53..2b338e7d9 100644 --- a/source/core/annotations/ut_annotated_objects.tps +++ b/source/core/annotations/ut_annotated_objects.tps @@ -1,7 +1,7 @@ -create type ut_annotated_objects as +create or replace noneditionable type ut_annotated_objects as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_annotation.tps b/source/core/annotations/ut_annotation.tps index 3d0311571..cc1f90e86 100644 --- a/source/core/annotations/ut_annotation.tps +++ b/source/core/annotations/ut_annotation.tps @@ -1,7 +1,7 @@ -create type ut_annotation as object( +create or replace noneditionable type ut_annotation as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_annotation_cache.sql b/source/core/annotations/ut_annotation_cache.sql index 67149ce60..7d69b1db6 100644 --- a/source/core/annotations/ut_annotation_cache.sql +++ b/source/core/annotations/ut_annotation_cache.sql @@ -1,7 +1,7 @@ create table ut_annotation_cache ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/annotations/ut_annotation_cache_info.sql b/source/core/annotations/ut_annotation_cache_info.sql index 167973a04..6248f4fbc 100644 --- a/source/core/annotations/ut_annotation_cache_info.sql +++ b/source/core/annotations/ut_annotation_cache_info.sql @@ -1,7 +1,7 @@ create table ut_annotation_cache_info ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/annotations/ut_annotation_cache_manager.pkb b/source/core/annotations/ut_annotation_cache_manager.pkb index e1131b3bd..afc473404 100644 --- a/source/core/annotations/ut_annotation_cache_manager.pkb +++ b/source/core/annotations/ut_annotation_cache_manager.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_annotation_cache_manager as +create or replace noneditionable package body ut_annotation_cache_manager as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_annotation_cache_manager.pks b/source/core/annotations/ut_annotation_cache_manager.pks index 1e9734934..1aa4cb501 100644 --- a/source/core/annotations/ut_annotation_cache_manager.pks +++ b/source/core/annotations/ut_annotation_cache_manager.pks @@ -1,7 +1,7 @@ -create or replace package ut_annotation_cache_manager authid definer as +create or replace noneditionable package ut_annotation_cache_manager authid definer as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_annotation_cache_schema.sql b/source/core/annotations/ut_annotation_cache_schema.sql index 8889b9abf..b8ebaa40b 100644 --- a/source/core/annotations/ut_annotation_cache_schema.sql +++ b/source/core/annotations/ut_annotation_cache_schema.sql @@ -1,7 +1,7 @@ create table ut_annotation_cache_schema ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/annotations/ut_annotation_cache_seq.sql b/source/core/annotations/ut_annotation_cache_seq.sql index b371b382b..f0f1f17d7 100644 --- a/source/core/annotations/ut_annotation_cache_seq.sql +++ b/source/core/annotations/ut_annotation_cache_seq.sql @@ -1,7 +1,7 @@ create sequence ut_annotation_cache_seq /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/annotations/ut_annotation_manager.pkb b/source/core/annotations/ut_annotation_manager.pkb index 16923bc23..b35f40075 100644 --- a/source/core/annotations/ut_annotation_manager.pkb +++ b/source/core/annotations/ut_annotation_manager.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_annotation_manager as +create or replace noneditionable package body ut_annotation_manager as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -121,6 +121,7 @@ create or replace package body ut_annotation_manager as and s.type = r.object_type where s.owner = ']'||ut_utils.qualified_sql_name(a_object_owner)||q'[' and s.type = ']'||ut_utils.qualified_sql_name(a_object_type)||q'[' + and s.origin_con_id = sys_context('userenv', 'con_id') ) x where x.is_annotated = 'Y' order by x.name, x.line]' @@ -143,34 +144,37 @@ create or replace package body ut_annotation_manager as l_parse_time date := sysdate; pragma autonomous_transaction; begin - loop - fetch a_sources_cursor bulk collect into l_names, l_lines limit c_lines_fetch_limit; - for i in 1 .. l_names.count loop - if l_names(i) != l_name then - l_annotations := ut_annotation_parser.parse_object_annotations(l_object_lines, a_object_type); - ut_annotation_cache_manager.update_cache( - ut_annotated_object(a_object_owner, l_name, a_object_type, l_parse_time, l_annotations) - ); - l_object_lines.delete; - end if; - - l_name := l_names(i); - l_object_lines(l_object_lines.count+1) := l_lines(i); - end loop; - exit when a_sources_cursor%notfound; + begin + loop + fetch a_sources_cursor bulk collect into l_names, l_lines limit c_lines_fetch_limit; + for i in 1 .. l_names.count loop + if l_names(i) != l_name then + l_annotations := ut_annotation_parser.parse_object_annotations(l_object_lines, a_object_type); + ut_annotation_cache_manager.update_cache( + ut_annotated_object(a_object_owner, l_name, a_object_type, l_parse_time, l_annotations) + ); + l_object_lines.delete; + end if; + + l_name := l_names(i); + l_object_lines(l_object_lines.count+1) := l_lines(i); + end loop; + exit when a_sources_cursor%notfound; - end loop; - if a_sources_cursor%rowcount > 0 then - l_annotations := ut_annotation_parser.parse_object_annotations(l_object_lines, a_object_type); - ut_annotation_cache_manager.update_cache( - ut_annotated_object(a_object_owner, l_name, a_object_type, l_parse_time, l_annotations) - ); - l_object_lines.delete; + end loop; + if a_sources_cursor%rowcount > 0 then + l_annotations := ut_annotation_parser.parse_object_annotations(l_object_lines, a_object_type); + ut_annotation_cache_manager.update_cache( + ut_annotated_object(a_object_owner, l_name, a_object_type, l_parse_time, l_annotations) + ); + l_object_lines.delete; + end if; + end; + if a_sources_cursor%isopen then + close a_sources_cursor; end if; - close a_sources_cursor; end; - procedure validate_annotation_cache( a_object_owner varchar2, a_object_type varchar2, @@ -231,23 +235,24 @@ create or replace package body ut_annotation_manager as function get_source_from_sql_text(a_object_name varchar2, a_sql_text ora_name_list_t, a_parts binary_integer) return sys_refcursor is l_sql_clob clob; - l_sql_lines ut_varchar2_rows := ut_varchar2_rows(); + l_sql_lines dbms_preprocessor.source_lines_t := dbms_preprocessor.source_lines_t(); + l_sql_lines_clob ut_varchar2_list := ut_varchar2_list(); l_result sys_refcursor; begin if a_parts > 0 then for i in 1..a_parts loop ut_utils.append_to_clob(l_sql_clob, a_sql_text(i)); end loop; - l_sql_clob := ut_utils.replace_multiline_comments(l_sql_clob); - -- replace comment lines that contain "-- create or replace" - l_sql_clob := regexp_replace(l_sql_clob, '^.*[-]{2,}\s*create(\s+or\s+replace).*$', modifier => 'mi'); - -- remove the "create [or replace] [[non]editionable] " so that we have only "type|package" for parsing - -- needed for dbms_preprocessor - 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_lines := ut_utils.convert_collection( ut_utils.clob_to_table(l_sql_clob) ); + + l_sql_lines_clob := ut_utils.clob_to_table(l_sql_clob); + for i in 1..l_sql_lines_clob.count loop + l_sql_lines(i) := l_sql_lines_clob(i); + end loop; + + -- replace multiline comments that contain "-- create or replace" with single line comment to avoid parsing issues with dbms_preprocessor + l_sql_lines := ut_utils.replace_multiline_comments(l_sql_lines); + -- strip CREATE header (possibly split across lines) while preserving line numbers + l_sql_lines := ut_utils.strip_create_header_lines(l_sql_lines); end if; open l_result for select /*+ no_parallel */ a_object_name as name, column_value||chr(10) as text from table(l_sql_lines); @@ -264,6 +269,7 @@ create or replace package body ut_annotation_manager as where s.type = :a_object_type and s.owner = :a_object_owner and s.name = :a_object_name + and s.origin_con_id = sys_context('userenv', 'con_id') order by s.line]' using a_object_name, a_object_type, a_object_owner, a_object_name; return l_result; diff --git a/source/core/annotations/ut_annotation_manager.pks b/source/core/annotations/ut_annotation_manager.pks index 20fcd810f..eedef002a 100644 --- a/source/core/annotations/ut_annotation_manager.pks +++ b/source/core/annotations/ut_annotation_manager.pks @@ -1,7 +1,7 @@ -create or replace package ut_annotation_manager authid current_user as +create or replace noneditionable package ut_annotation_manager authid current_user as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_annotation_obj_cache_info.tps b/source/core/annotations/ut_annotation_obj_cache_info.tps index 1db3fd190..5436a11ed 100644 --- a/source/core/annotations/ut_annotation_obj_cache_info.tps +++ b/source/core/annotations/ut_annotation_obj_cache_info.tps @@ -1,7 +1,7 @@ -create type ut_annotation_obj_cache_info as object( +create or replace noneditionable type ut_annotation_obj_cache_info as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_annotation_objs_cache_info.tps b/source/core/annotations/ut_annotation_objs_cache_info.tps index fc96e7d25..c29b7d208 100644 --- a/source/core/annotations/ut_annotation_objs_cache_info.tps +++ b/source/core/annotations/ut_annotation_objs_cache_info.tps @@ -1,7 +1,7 @@ -create type ut_annotation_objs_cache_info as +create or replace noneditionable type ut_annotation_objs_cache_info as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_annotation_parser.pkb b/source/core/annotations/ut_annotation_parser.pkb index f0271c957..5680d3cd2 100644 --- a/source/core/annotations/ut_annotation_parser.pkb +++ b/source/core/annotations/ut_annotation_parser.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_annotation_parser as +create or replace noneditionable package body ut_annotation_parser as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -22,12 +22,10 @@ create or replace package body ut_annotation_parser as type tt_comment_list is table of varchar2(32767) index by binary_integer; gc_annotation_qualifier constant varchar2(1) := '%'; - 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_annotation_block_pattern constant varchar2(200) := '(({COMMENT#.+}'||chr(10)||')+)( |'||chr(09)||')*(procedure|function)\s+(' || - gc_regexp_identifier || ')'; + gc_regexp_identifier constant varchar2(50) := '[[:alpha:]][[:alnum:]$#_]*'; + gc_annotation_pattern constant varchar2(50) := gc_annotation_qualifier || gc_regexp_identifier || '[ '||chr(9)||']*(\(.*?\)\s*?$)?'; @@ -72,133 +70,144 @@ create or replace package body ut_annotation_parser as a_subobject_name varchar2 := null ) is l_loop_index binary_integer := 1; - l_annotation_index binary_integer; + l_annotation_index binary_integer := 1; begin - -- loop while there are unprocessed comment blocks - while 0 != nvl(regexp_instr(srcstr => a_source - ,pattern => gc_comment_replacer_regex_ptrn - ,occurrence => l_loop_index - ,subexpression => 1) - ,0) loop + -- loop while there are unprocessed comment blocks + while l_annotation_index is not null loop -- define index of the comment block and get it's content from cache l_annotation_index := regexp_substr( a_source ,gc_comment_replacer_regex_ptrn ,1 ,l_loop_index ,subexpression => 1); - add_annotation( a_annotations, l_annotation_index, a_comments( l_annotation_index ), a_subobject_name ); - l_loop_index := l_loop_index + 1; + if l_annotation_index is not null then + add_annotation( a_annotations, l_annotation_index, a_comments( l_annotation_index ), a_subobject_name ); + l_loop_index := l_loop_index + 1; + end if; end loop; end add_annotations; - procedure add_procedure_annotations(a_annotations in out nocopy ut_annotations, a_source clob, a_comments in out nocopy tt_comment_list) is - l_proc_comments varchar2(32767); - l_proc_name varchar2(250); - l_annot_proc_ind number; - l_annot_proc_block varchar2(32767); + procedure add_procedure_annotations( + a_annotations in out nocopy ut_annotations, + a_source in dbms_preprocessor.source_lines_t, + a_comments in out nocopy tt_comment_list + ) is + l_proc_comments varchar2(32767); + l_proc_name varchar2(250); + l_line varchar2(32767); + l_in_comment_block boolean := false; + l_i binary_integer; begin - -- loop through procedures and functions of the package and get all the comment blocks just before it's declaration - l_annot_proc_ind := 1; - loop - --find annotated procedure index - l_annot_proc_ind := regexp_instr(srcstr => a_source - ,pattern => gc_annotation_block_pattern - ,occurrence => 1 - ,modifier => 'i' - ,position => l_annot_proc_ind); - exit when l_annot_proc_ind = 0; - - --get the annotations with procedure name - l_annot_proc_block := regexp_substr(srcstr => a_source - ,pattern => gc_annotation_block_pattern - ,position => l_annot_proc_ind - ,occurrence => 1 - ,modifier => 'i'); - - --extract the annotations - l_proc_comments := trim(regexp_substr(srcstr => l_annot_proc_block - ,pattern => gc_annotation_block_pattern - ,modifier => 'i' - ,subexpression => 1)); - --extract the procedure name - l_proc_name := trim(regexp_substr(srcstr => l_annot_proc_block - ,pattern => gc_annotation_block_pattern - ,modifier => 'i' - ,subexpression => 5)); - - -- parse the comment block for the syntactically correct annotations and store them as an array - add_annotations(a_annotations, l_proc_comments, a_comments, l_proc_name); - - l_annot_proc_ind := instr(a_source, ';', l_annot_proc_ind + length(l_annot_proc_block) ); + l_i := 1; + while l_i <= a_source.count loop + l_line := a_source(l_i); + -- Comment placeholder line: start/continue accumulating a block + if instr(l_line, chr(123) ||'COMMENT#') > 0 then + if l_in_comment_block then + l_proc_comments := l_proc_comments || l_line || chr(10); + else + l_in_comment_block := true; + l_proc_comments := l_line || chr(10); + end if; + l_i := l_i + 1; + -- Whitespace-only line: allowed between comment block and proc decl + elsif l_in_comment_block and trim(replace(l_line, chr(9))) is null then + l_i := l_i + 1; + -- procedure/function declaration following a comment block + elsif l_in_comment_block + and regexp_like(l_line, '^\s*(procedure|function)\s+', 'i') + then + -- extract just the identifier name (subexpression 2) + l_proc_name := trim(regexp_substr(srcstr =>l_line + ,pattern => '^\s*(procedure|function)\s+('||gc_regexp_identifier||')' + ,modifier => 'i' + ,subexpression => 2)); + + -- pass accumulated comment placeholders + proc name to add_annotations + add_annotations(a_annotations, l_proc_comments, a_comments, l_proc_name); + + -- reset comment block state + l_in_comment_block := false; + l_proc_comments := null; + + -- advance past proc header to the terminating ';' + -- the header may span multiple lines e.g. with multi-line parameter lists + while l_i <= a_source.count loop + exit when instr(a_source(l_i), ';') > 0; + l_i := l_i + 1; + end loop; + l_i := l_i + 1; -- step past the ';' line itself + -- Any other line: reset comment block accumulator + else + l_in_comment_block := false; + l_proc_comments := null; + l_i := l_i + 1; + end if; end loop; end add_procedure_annotations; - function extract_and_replace_comments(a_source in out nocopy clob) return tt_comment_list is - l_comments tt_comment_list; - l_comment_pos binary_integer; - l_comment_line binary_integer; + function extract_and_replace_comments( + a_source in out nocopy dbms_preprocessor.source_lines_t + ) return tt_comment_list is + l_comments tt_comment_list; + l_line varchar2(32767); + l_comment_pos binary_integer; l_comment_replacer varchar2(50); - l_source clob := a_source; begin - l_comment_pos := 1; - loop - - l_comment_pos := regexp_instr(srcstr => a_source - ,pattern => gc_annot_comment_pattern - ,occurrence => 1 - ,modifier => 'm' - ,position => l_comment_pos); - - exit when l_comment_pos = 0; - - -- position index is shifted by 1 because gc_annot_comment_pattern contains ^ as first sign - -- but after instr index already points to the char on that line - l_comment_pos := l_comment_pos-1; - l_comment_line := length(substr(a_source,1,l_comment_pos))-length(replace(substr(a_source,1,l_comment_pos),chr(10)))+1; - l_comments(l_comment_line) := trim(regexp_substr(srcstr => a_source - ,pattern => gc_annot_comment_pattern - ,occurrence => 1 - ,position => l_comment_pos - ,modifier => 'm' - ,subexpression => 2)); - - l_comment_replacer := replace(gc_comment_replacer_patter, '%N%', l_comment_line); - - l_source := regexp_replace(srcstr => a_source - ,pattern => gc_annot_comment_pattern - ,replacestr => l_comment_replacer - ,position => l_comment_pos - ,occurrence => 1 - ,modifier => 'm'); - dbms_lob.freetemporary(a_source); - a_source := l_source; - dbms_lob.freetemporary(l_source); - l_comment_pos := l_comment_pos + length(l_comment_replacer); + for i in 1 .. a_source.count loop + l_line := a_source(i); + + -- fast path: skip lines that can't possibly match + -- must contain '--' and '%' to be an annotation comment + if instr(l_line, '--') = 0 or instr(l_line, gc_annotation_qualifier) = 0 then + continue; + end if; + + -- find '--' on the line + l_comment_pos := instr(l_line, '--'); + + -- verify everything before '--' is only spaces/tabs (matches ^ *( |\t)*--) + if trim(replace(substr(l_line, 1, l_comment_pos - 1), chr(9))) is not null then + continue; + end if; + + -- skip '--' and any spaces after it, then check for annotation qualifier '%' + l_comment_pos := l_comment_pos + 2; + -- skip optional spaces between -- and % + while l_comment_pos <= length(l_line) + and substr(l_line, l_comment_pos, 1) = ' ' + loop + l_comment_pos := l_comment_pos + 1; + end loop; + + -- must start with annotation qualifier at this position + if substr(l_line, l_comment_pos, 1) != gc_annotation_qualifier then + continue; + end if; + + -- extract annotation text (from % to end of line, trimmed) + l_comments(i) := trim(substr(l_line, l_comment_pos)); + + -- replace line with placeholder, preserving line number in token + l_comment_replacer := replace(gc_comment_replacer_patter, '%N%', i); + a_source(i) := l_comment_replacer; end loop; - ut_utils.debug_log(a_source); return l_comments; end extract_and_replace_comments; ------------------------------------------------------------ --public definitions ------------------------------------------------------------ - - function parse_object_annotations(a_source clob) return ut_annotations is - l_source clob := a_source; + function parse_object_annotations(a_source dbms_preprocessor.source_lines_t) return ut_annotations is + l_source dbms_preprocessor.source_lines_t := a_source; l_comments tt_comment_list; l_annotations ut_annotations := ut_annotations(); l_result ut_annotations; l_comment_index positive; begin - - l_source := ut_utils.replace_multiline_comments(l_source); - - -- replace all single line comments with {COMMENT#12} element and store it's content for easier processing - -- this call modifies l_source + l_source := ut_utils.replace_multiline_comments(l_source); l_comments := extract_and_replace_comments(l_source); - add_procedure_annotations(l_annotations, l_source, l_comments); - delete_processed_comments(l_comments, l_annotations); --at this point, only the comments not related to procedures are left, so we process them all as top-level @@ -208,16 +217,15 @@ create or replace package body ut_annotation_parser as l_comment_index := l_comments.next(l_comment_index); end loop; - dbms_lob.freetemporary(l_source); - - select /*+ no_parallel */ value(x) bulk collect into l_result from table(l_annotations) x order by x.position; + select /*+ no_parallel */ value(x) bulk collect into l_result from table(l_annotations) x order by x.position asc; return l_result; + end parse_object_annotations; function parse_object_annotations(a_source_lines dbms_preprocessor.source_lines_t, a_object_type varchar2) return ut_annotations is l_processed_lines dbms_preprocessor.source_lines_t; - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_annotations ut_annotations := ut_annotations(); ex_package_is_wrapped exception; pragma exception_init(ex_package_is_wrapped, -24241); @@ -233,13 +241,11 @@ create or replace package body ut_annotation_parser as else l_processed_lines := sys.dbms_preprocessor.get_post_processed_source(a_source_lines); end if; - --convert to clob for i in 1..l_processed_lines.count loop - ut_utils.append_to_clob(l_source, replace(l_processed_lines(i), chr(13)||chr(10), chr(10))); + l_source(i) := replace(l_processed_lines(i), chr(13)||chr(10), chr(10)); end loop; --parse annotations l_annotations := parse_object_annotations(l_source); - dbms_lob.freetemporary(l_source); exception when ex_package_is_wrapped or source_text_is_empty then null; diff --git a/source/core/annotations/ut_annotation_parser.pks b/source/core/annotations/ut_annotation_parser.pks index 2f474c883..3e0eaf84c 100644 --- a/source/core/annotations/ut_annotation_parser.pks +++ b/source/core/annotations/ut_annotation_parser.pks @@ -1,7 +1,7 @@ -create or replace package ut_annotation_parser authid current_user as +create or replace noneditionable package ut_annotation_parser authid current_user as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -29,16 +29,5 @@ create or replace package ut_annotation_parser authid current_user as */ function parse_object_annotations(a_source_lines dbms_preprocessor.source_lines_t, a_object_type varchar2) return ut_annotations; - - /** - * - * @private - * Parses source code and converts it to annotations - * - * @param a_source_lines ordered lines of source code to be parsed - * @return array containing annotations - */ - function parse_object_annotations(a_source clob) return ut_annotations; - end; / diff --git a/source/core/annotations/ut_annotations.tps b/source/core/annotations/ut_annotations.tps index a6579d236..9e4adc5ae 100644 --- a/source/core/annotations/ut_annotations.tps +++ b/source/core/annotations/ut_annotations.tps @@ -1,7 +1,7 @@ -create type ut_annotations +create or replace noneditionable type ut_annotations /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/annotations/ut_trigger_annotation_parsing.trg b/source/core/annotations/ut_trigger_annotation_parsing.trg index 437b7742d..75caea5e8 100644 --- a/source/core/annotations/ut_trigger_annotation_parsing.trg +++ b/source/core/annotations/ut_trigger_annotation_parsing.trg @@ -2,6 +2,22 @@ create or replace trigger ut_trigger_annotation_parsing after create or alter or drop on database begin + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. + */ if (ora_dict_obj_owner = UPPER('&&UT3_OWNER') and ora_dict_obj_name = 'UT3_TRIGGER_ALIVE' and ora_dict_obj_type = 'SYNONYM') diff --git a/source/core/annotations/ut_trigger_check.pkb b/source/core/annotations/ut_trigger_check.pkb index 34e43ba46..463243c5e 100644 --- a/source/core/annotations/ut_trigger_check.pkb +++ b/source/core/annotations/ut_trigger_check.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_trigger_check is +create or replace noneditionable package body ut_trigger_check is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ create or replace package body ut_trigger_check is function is_alive return boolean is pragma autonomous_transaction; begin - execute immediate 'create or replace synonym '||ut_utils.ut_owner||'.'||gc_check_object_name||' for no_object'; + execute immediate 'create or replace noneditionable synonym '||ut_utils.ut_owner||'.'||gc_check_object_name||' for no_object'; return g_is_trigger_live; end; diff --git a/source/core/annotations/ut_trigger_check.pks b/source/core/annotations/ut_trigger_check.pks index cee0b06fd..dae59f3aa 100644 --- a/source/core/annotations/ut_trigger_check.pks +++ b/source/core/annotations/ut_trigger_check.pks @@ -1,7 +1,7 @@ -create or replace package ut_trigger_check authid definer is +create or replace noneditionable package ut_trigger_check authid definer is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage.pkb b/source/core/coverage/ut_coverage.pkb index f5eccc893..be9b4ad5d 100644 --- a/source/core/coverage/ut_coverage.pkb +++ b/source/core/coverage/ut_coverage.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_coverage is +create or replace noneditionable package body ut_coverage is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -55,10 +55,11 @@ create or replace package body ut_coverage is s.text from {sources_view} s {join_file_mappings} where s.type in ('PACKAGE BODY', 'TYPE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER') + and s.origin_con_id = sys_context('userenv', 'con_id') {filters} {regex_exc_filters} and not exists ( - select /*+ cardinality(el {excuded_objects_cardinality})*/ 1 + select /*+ cardinality(el {excluded_objects_cardinality})*/ 1 from table(:l_excluded_objects) el where s.owner = el.owner and s.name = el.name ) @@ -152,7 +153,7 @@ create or replace package body ut_coverage is l_result := replace(l_result, '{filters}', l_filters); l_result := replace(l_result, '{mappings_cardinality}', l_mappings_cardinality); l_result := replace(l_result, '{skipped_objects_cardinality}', ut_utils.scale_cardinality(cardinality(a_skip_objects))); - l_result := replace(l_result, '{excuded_objects_cardinality}', ut_utils.scale_cardinality(cardinality(coalesce(a_coverage_options.exclude_objects, ut_object_names())))); + l_result := replace(l_result, '{excluded_objects_cardinality}', ut_utils.scale_cardinality(cardinality(coalesce(a_coverage_options.exclude_objects, ut_object_names())))); l_result := replace(l_result, '{regex_exc_filters}', l_regex_exc_filters); return l_result; @@ -167,7 +168,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.pks b/source/core/coverage/ut_coverage.pks index 21f3b1f9e..b8bc4e169 100644 --- a/source/core/coverage/ut_coverage.pks +++ b/source/core/coverage/ut_coverage.pks @@ -1,7 +1,7 @@ -create or replace package ut_coverage authid current_user is +create or replace noneditionable package ut_coverage authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_block.pkb b/source/core/coverage/ut_coverage_block.pkb index a906b81a3..bfdf13274 100644 --- a/source/core/coverage/ut_coverage_block.pkb +++ b/source/core/coverage/ut_coverage_block.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_coverage_block is +create or replace noneditionable package body ut_coverage_block is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_block.pks b/source/core/coverage/ut_coverage_block.pks index 01caca035..9fccb3272 100644 --- a/source/core/coverage/ut_coverage_block.pks +++ b/source/core/coverage/ut_coverage_block.pks @@ -1,7 +1,7 @@ -create or replace package ut_coverage_block authid current_user is +create or replace noneditionable package ut_coverage_block authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_helper.pkb b/source/core/coverage/ut_coverage_helper.pkb index e249b3381..ed1bea2ce 100644 --- a/source/core/coverage/ut_coverage_helper.pkb +++ b/source/core/coverage/ut_coverage_helper.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_coverage_helper is +create or replace noneditionable package body ut_coverage_helper is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_helper.pks b/source/core/coverage/ut_coverage_helper.pks index cd6ed9ec7..11988d935 100644 --- a/source/core/coverage/ut_coverage_helper.pks +++ b/source/core/coverage/ut_coverage_helper.pks @@ -1,7 +1,7 @@ -create or replace package ut_coverage_helper authid definer is +create or replace noneditionable package ut_coverage_helper authid definer is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_helper_block.pkb b/source/core/coverage/ut_coverage_helper_block.pkb index fb4d4812e..3abbd54fc 100644 --- a/source/core/coverage/ut_coverage_helper_block.pkb +++ b/source/core/coverage/ut_coverage_helper_block.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_coverage_helper_block is +create or replace noneditionable package body ut_coverage_helper_block is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_helper_block.pks b/source/core/coverage/ut_coverage_helper_block.pks index a147d9f4f..dd8c451d7 100644 --- a/source/core/coverage/ut_coverage_helper_block.pks +++ b/source/core/coverage/ut_coverage_helper_block.pks @@ -1,7 +1,7 @@ -create or replace package ut_coverage_helper_block authid current_user is +create or replace noneditionable package ut_coverage_helper_block authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_helper_profiler.pkb b/source/core/coverage/ut_coverage_helper_profiler.pkb index f29291efb..f98631593 100644 --- a/source/core/coverage/ut_coverage_helper_profiler.pkb +++ b/source/core/coverage/ut_coverage_helper_profiler.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_coverage_helper_profiler is +create or replace noneditionable package body ut_coverage_helper_profiler is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_helper_profiler.pks b/source/core/coverage/ut_coverage_helper_profiler.pks index 0c54a83bf..ca2a44a64 100644 --- a/source/core/coverage/ut_coverage_helper_profiler.pks +++ b/source/core/coverage/ut_coverage_helper_profiler.pks @@ -1,7 +1,7 @@ -create or replace package ut_coverage_helper_profiler authid definer is +create or replace noneditionable package ut_coverage_helper_profiler authid definer is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_profiler.pkb b/source/core/coverage/ut_coverage_profiler.pkb index 663ceab7f..4e874dabf 100644 --- a/source/core/coverage/ut_coverage_profiler.pkb +++ b/source/core/coverage/ut_coverage_profiler.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_coverage_profiler is +create or replace noneditionable package body ut_coverage_profiler is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_profiler.pks b/source/core/coverage/ut_coverage_profiler.pks index 9ce9a27f0..788005e15 100644 --- a/source/core/coverage/ut_coverage_profiler.pks +++ b/source/core/coverage/ut_coverage_profiler.pks @@ -1,7 +1,7 @@ -create or replace package ut_coverage_profiler authid current_user is +create or replace noneditionable package ut_coverage_profiler authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_reporter_base.tpb b/source/core/coverage/ut_coverage_reporter_base.tpb index aff4e6560..e86af3795 100644 --- a/source/core/coverage/ut_coverage_reporter_base.tpb +++ b/source/core/coverage/ut_coverage_reporter_base.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_coverage_reporter_base is +create or replace noneditionable type body ut_coverage_reporter_base is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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/coverage/ut_coverage_reporter_base.tps b/source/core/coverage/ut_coverage_reporter_base.tps index 305c5d757..c4afe389b 100644 --- a/source/core/coverage/ut_coverage_reporter_base.tps +++ b/source/core/coverage/ut_coverage_reporter_base.tps @@ -1,7 +1,7 @@ -create or replace type ut_coverage_reporter_base under ut_output_reporter_base( +create or replace noneditionable type ut_coverage_reporter_base under ut_output_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/coverage/ut_coverage_sources_tmp.sql b/source/core/coverage/ut_coverage_sources_tmp.sql index c091d5955..35eb05510 100644 --- a/source/core/coverage/ut_coverage_sources_tmp.sql +++ b/source/core/coverage/ut_coverage_sources_tmp.sql @@ -1,7 +1,7 @@ create global temporary table ut_coverage_sources_tmp( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/events/ut_event_item.tps b/source/core/events/ut_event_item.tps index c0ea653ea..d9e1b84fc 100644 --- a/source/core/events/ut_event_item.tps +++ b/source/core/events/ut_event_item.tps @@ -1,7 +1,7 @@ -create or replace type ut_event_item authid current_user as object ( +create or replace noneditionable type ut_event_item authid current_user as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/events/ut_event_listener.tps b/source/core/events/ut_event_listener.tps index bbcdbf218..7236fd512 100644 --- a/source/core/events/ut_event_listener.tps +++ b/source/core/events/ut_event_listener.tps @@ -1,7 +1,7 @@ -create or replace type ut_event_listener authid current_user as object ( +create or replace noneditionable type ut_event_listener authid current_user as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/events/ut_event_manager.pkb b/source/core/events/ut_event_manager.pkb index f51019421..50c500d13 100644 --- a/source/core/events/ut_event_manager.pkb +++ b/source/core/events/ut_event_manager.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_event_manager as +create or replace noneditionable package body ut_event_manager as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/events/ut_event_manager.pks b/source/core/events/ut_event_manager.pks index 03b18b728..08db7f5da 100644 --- a/source/core/events/ut_event_manager.pks +++ b/source/core/events/ut_event_manager.pks @@ -1,7 +1,7 @@ -create or replace package ut_event_manager authid current_user as +create or replace noneditionable package ut_event_manager authid current_user as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/output_buffers/ut_output_buffer_base.tpb b/source/core/output_buffers/ut_output_buffer_base.tpb index 6d7964150..8b9c236ef 100644 --- a/source/core/output_buffers/ut_output_buffer_base.tpb +++ b/source/core/output_buffers/ut_output_buffer_base.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_output_buffer_base is +create or replace noneditionable type body ut_output_buffer_base is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -18,24 +18,101 @@ create or replace type body ut_output_buffer_base is member procedure init(self in out nocopy ut_output_buffer_base, a_output_id raw := null, a_self_type varchar2 := null) is pragma autonomous_transaction; - l_exists int; begin cleanup_buffer(); 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; - else - insert /*+ no_parallel */ into ut_output_buffer_info_tmp(output_id, start_date) values (self.output_id, self.start_date); - end if; + merge /*+ no_parallel */ into ut_output_buffer_info_tmp dst + using (select 1 from dual) src + on (dst.output_id = self.output_id) + when matched then + update + set dst.start_date = self.start_date + when not matched then + insert (output_id, start_date) + values (self.output_id, self.start_date); 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 +121,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 +131,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..da2bc9143 100644 --- a/source/core/output_buffers/ut_output_buffer_base.tps +++ b/source/core/output_buffers/ut_output_buffer_base.tps @@ -1,7 +1,7 @@ -create or replace type ut_output_buffer_base force authid definer as object( +create or replace noneditionable type ut_output_buffer_base force authid definer as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_info_tmp.sql b/source/core/output_buffers/ut_output_buffer_info_tmp.sql index af730d394..2aba5a55c 100644 --- a/source/core/output_buffers/ut_output_buffer_info_tmp.sql +++ b/source/core/output_buffers/ut_output_buffer_info_tmp.sql @@ -1,7 +1,7 @@ create table ut_output_buffer_info_tmp( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/output_buffers/ut_output_buffer_tmp.sql b/source/core/output_buffers/ut_output_buffer_tmp.sql index 57027817e..68b738d3f 100644 --- a/source/core/output_buffers/ut_output_buffer_tmp.sql +++ b/source/core/output_buffers/ut_output_buffer_tmp.sql @@ -1,7 +1,7 @@ create table ut_output_buffer_tmp( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 @@ -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..4f6698eaf --- /dev/null +++ b/source/core/output_buffers/ut_output_bulk_buffer.tpb @@ -0,0 +1,160 @@ +create or replace noneditionable type body ut_output_bulk_buffer is + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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..538bc12a5 --- /dev/null +++ b/source/core/output_buffers/ut_output_bulk_buffer.tps @@ -0,0 +1,27 @@ +create or replace noneditionable type ut_output_bulk_buffer under ut_output_buffer_base ( + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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..fa61191ce 100644 --- a/source/core/output_buffers/ut_output_clob_buffer_tmp.sql +++ b/source/core/output_buffers/ut_output_clob_buffer_tmp.sql @@ -6,7 +6,7 @@ begin v_table_sql := 'create table ut_output_clob_buffer_tmp( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 @@ -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..f6d7c9a66 100644 --- a/source/core/output_buffers/ut_output_clob_table_buffer.tpb +++ b/source/core/output_buffers/ut_output_clob_table_buffer.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_output_clob_table_buffer is +create or replace noneditionable type body ut_output_clob_table_buffer is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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..b254c1f47 100644 --- a/source/core/output_buffers/ut_output_clob_table_buffer.tps +++ b/source/core/output_buffers/ut_output_clob_table_buffer.tps @@ -1,7 +1,7 @@ -create or replace type ut_output_clob_table_buffer under ut_output_buffer_base ( +create or replace noneditionable type ut_output_clob_table_buffer under ut_output_buffer_base ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_data_row.tps b/source/core/output_buffers/ut_output_data_row.tps index c5dd95e98..2eed2ee1f 100644 --- a/source/core/output_buffers/ut_output_data_row.tps +++ b/source/core/output_buffers/ut_output_data_row.tps @@ -1,7 +1,7 @@ -create or replace type ut_output_data_row as object ( +create or replace noneditionable type ut_output_data_row as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/output_buffers/ut_output_data_rows.tps b/source/core/output_buffers/ut_output_data_rows.tps index 097e9beff..a10440617 100644 --- a/source/core/output_buffers/ut_output_data_rows.tps +++ b/source/core/output_buffers/ut_output_data_rows.tps @@ -1,7 +1,7 @@ -create or replace type ut_output_data_rows as +create or replace noneditionable type ut_output_data_rows as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/output_buffers/ut_output_table_buffer.tpb b/source/core/output_buffers/ut_output_table_buffer.tpb index 1809a49d5..d87362572 100644 --- a/source/core/output_buffers/ut_output_table_buffer.tpb +++ b/source/core/output_buffers/ut_output_table_buffer.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_output_table_buffer is +create or replace noneditionable type body ut_output_table_buffer is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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..3f13e736d 100644 --- a/source/core/output_buffers/ut_output_table_buffer.tps +++ b/source/core/output_buffers/ut_output_table_buffer.tps @@ -1,7 +1,7 @@ -create or replace type ut_output_table_buffer under ut_output_buffer_base ( +create or replace noneditionable type ut_output_table_buffer under ut_output_buffer_base ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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/session_context/ut_session_context.pkb b/source/core/session_context/ut_session_context.pkb index a97505e9c..12d104eed 100644 --- a/source/core/session_context/ut_session_context.pkb +++ b/source/core/session_context/ut_session_context.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_session_context as +create or replace noneditionable package body ut_session_context as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/session_context/ut_session_context.pks b/source/core/session_context/ut_session_context.pks index 63da3399e..a5e4038a0 100644 --- a/source/core/session_context/ut_session_context.pks +++ b/source/core/session_context/ut_session_context.pks @@ -1,7 +1,7 @@ -create or replace package ut_session_context as +create or replace noneditionable package ut_session_context as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/session_context/ut_session_info.tpb b/source/core/session_context/ut_session_info.tpb index 7d469651a..14f52b003 100644 --- a/source/core/session_context/ut_session_info.tpb +++ b/source/core/session_context/ut_session_info.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_session_info as +create or replace noneditionable type body ut_session_info as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/session_context/ut_session_info.tps b/source/core/session_context/ut_session_info.tps index 17e596d88..d682a6bad 100644 --- a/source/core/session_context/ut_session_info.tps +++ b/source/core/session_context/ut_session_info.tps @@ -1,7 +1,7 @@ -create or replace type ut_session_info under ut_event_listener ( +create or replace noneditionable type ut_session_info under ut_event_listener ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_console_reporter_base.tpb b/source/core/types/ut_console_reporter_base.tpb index 909e9355a..0825b8ba2 100644 --- a/source/core/types/ut_console_reporter_base.tpb +++ b/source/core/types/ut_console_reporter_base.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_console_reporter_base is +create or replace noneditionable type body ut_console_reporter_base is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_console_reporter_base.tps b/source/core/types/ut_console_reporter_base.tps index a2c4b6b8c..46b344e6e 100644 --- a/source/core/types/ut_console_reporter_base.tps +++ b/source/core/types/ut_console_reporter_base.tps @@ -1,7 +1,7 @@ -create or replace type ut_console_reporter_base under ut_output_reporter_base( +create or replace noneditionable type ut_console_reporter_base under ut_output_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_coverage_options.tpb b/source/core/types/ut_coverage_options.tpb index a091802af..2accd960a 100644 --- a/source/core/types/ut_coverage_options.tpb +++ b/source/core/types/ut_coverage_options.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_coverage_options as +create or replace noneditionable type body ut_coverage_options as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_coverage_options.tps b/source/core/types/ut_coverage_options.tps index a8e3d5129..f2db1dafe 100644 --- a/source/core/types/ut_coverage_options.tps +++ b/source/core/types/ut_coverage_options.tps @@ -1,7 +1,7 @@ -create or replace type ut_coverage_options force as object ( +create or replace noneditionable type ut_coverage_options force as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_executable.tpb b/source/core/types/ut_executable.tpb index d2ce9b79d..2fa3c1b5d 100644 --- a/source/core/types/ut_executable.tpb +++ b/source/core/types/ut_executable.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_executable is +create or replace noneditionable type body ut_executable is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -38,20 +38,30 @@ create or replace type body ut_executable is return ut_metadata.form_name(l_owner_name, object_name, procedure_name); end; - member procedure do_execute(self in out nocopy ut_executable, a_item in out nocopy ut_suite_item) is + member procedure do_execute( + self in out nocopy ut_executable, + a_item in out nocopy ut_suite_item, + a_ignored_exception_names in ut_varchar2_rows := null, + a_ignored_exception_numbers in ut_varchar2_rows :=null) + is l_completed_without_errors boolean; begin - l_completed_without_errors := self.do_execute(a_item); + l_completed_without_errors := self.do_execute(a_item, a_ignored_exception_names, a_ignored_exception_numbers); end do_execute; - member function do_execute(self in out nocopy ut_executable, a_item in out nocopy ut_suite_item) return boolean is - l_statement varchar2(4000); - l_status number; - l_cursor_number number; - l_completed_without_errors boolean := true; - l_failed_with_invalid_pck boolean := true; - l_start_transaction_id varchar2(250); - l_end_transaction_id varchar2(250); + member function do_execute( + self in out nocopy ut_executable, + a_item in out nocopy ut_suite_item, + a_ignored_exception_names in ut_varchar2_rows := null, + a_ignored_exception_numbers in ut_varchar2_rows :=null) + return boolean is + l_statement varchar2(4000); + l_status number; + l_cursor_number number; + l_completed_without_errors boolean := true; + l_failed_with_invalid_pck boolean := true; + l_start_transaction_id varchar2(250); + l_end_transaction_id varchar2(250); function is_defined return boolean is l_result boolean := false; @@ -111,19 +121,33 @@ create or replace type body ut_executable is if l_completed_without_errors then l_statement := 'declare' || chr(10) || - ' l_error_stack varchar2(32767);' || chr(10) || - ' l_error_backtrace varchar2(32767);' || chr(10) || + ' l_error_stack varchar2(32767);' || chr(10) || + ' l_error_backtrace varchar2(32767);' || chr(10) || + ' l_ignored_exception_detected integer := 0;' || chr(10) || 'begin' || chr(10) || ' begin' || chr(10) || ' ' || self.form_name( a_skip_current_user_schema => true ) || ';' || chr(10) || ' exception' || chr(10) || + case when a_ignored_exception_names is not empty then + ' when ' || ut_utils.table_to_clob( a_ignored_exception_names, ' or ' ) || ' then ' || chr(10) || + ' l_ignored_exception_detected := 1;' + end || ' when others then ' || chr(10) || + case when a_ignored_exception_numbers is not empty then + ' if sqlcode in (' || ut_utils.table_to_clob( a_ignored_exception_numbers, ', ' ) || ') then ' || chr(10) || + ' l_ignored_exception_detected := 1; ' || chr(10) || + ' else ' || chr(10) || + ' l_error_stack := dbms_utility.format_error_stack;' || chr(10) || + ' l_error_backtrace := dbms_utility.format_error_backtrace;' || chr(10) || + ' end if;' + else ' l_error_stack := dbms_utility.format_error_stack;' || chr(10) || - ' l_error_backtrace := dbms_utility.format_error_backtrace;' || chr(10) || - ' --raise on ORA-04068, ORA-04061: existing state of packages has been discarded to avoid unrecoverable session exception' || chr(10) || + ' l_error_backtrace := dbms_utility.format_error_backtrace;' + end || chr(10) || ' end;' || chr(10) || ' :a_error_stack := l_error_stack;' || chr(10) || ' :a_error_backtrace := l_error_backtrace;' || chr(10) || + ' :a_ignored_exception_detected := l_ignored_exception_detected;' || chr(10) || 'end;'; ut_utils.debug_log('ut_executable.do_execute l_statement: ' || l_statement); @@ -139,9 +163,11 @@ create or replace type body ut_executable is dbms_sql.parse(l_cursor_number, statement => l_statement, language_flag => dbms_sql.native); dbms_sql.bind_variable(l_cursor_number, 'a_error_stack', to_char(null), 32767); dbms_sql.bind_variable(l_cursor_number, 'a_error_backtrace', to_char(null), 32767); + dbms_sql.bind_variable(l_cursor_number, 'a_ignored_exception_detected', to_number(null)); l_status := dbms_sql.execute(l_cursor_number); dbms_sql.variable_value(l_cursor_number, 'a_error_stack', self.error_stack); dbms_sql.variable_value(l_cursor_number, 'a_error_backtrace', self.error_backtrace); + dbms_sql.variable_value(l_cursor_number, 'a_ignored_exception_detected', self.ignored_exception_detected); dbms_sql.close_cursor(l_cursor_number); exception when ut_utils.ex_invalid_package then diff --git a/source/core/types/ut_executable.tps b/source/core/types/ut_executable.tps index aaa5a3a72..0945a8996 100644 --- a/source/core/types/ut_executable.tps +++ b/source/core/types/ut_executable.tps @@ -1,7 +1,7 @@ -create or replace type ut_executable under ut_event_item( +create or replace noneditionable type ut_executable under ut_event_item( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -18,26 +18,37 @@ create or replace type ut_executable under ut_event_item( /** * The name of the event to be executed before and after the executable is invoked */ - executable_type varchar2(250 char), - owner_name varchar2(250 char), - object_name varchar2(250 char), - procedure_name varchar2(250 char), - error_backtrace varchar2(4000), - error_stack varchar2(4000), - serveroutput clob, + executable_type varchar2(250 char), + owner_name varchar2(250 char), + object_name varchar2(250 char), + procedure_name varchar2(250 char), + error_backtrace varchar2(4000), + error_stack varchar2(4000), + ignored_exception_detected integer, + serveroutput clob, /** * Used for ordering of executables, as Oracle doesn not guarantee ordering of items in a nested table. */ seq_no integer, constructor function ut_executable( self in out nocopy ut_executable, a_owner varchar2, a_package varchar2, a_procedure_name varchar2, a_executable_type varchar2) return self as result, member function form_name(a_skip_current_user_schema boolean := false) return varchar2, - member procedure do_execute(self in out nocopy ut_executable, a_item in out nocopy ut_suite_item), + member procedure do_execute( + self in out nocopy ut_executable, + a_item in out nocopy ut_suite_item, + a_ignored_exception_names in ut_varchar2_rows := null, + a_ignored_exception_numbers in ut_varchar2_rows := null + ), /** * executes the defines executable * returns true if executed without exceptions * returns false if exceptions were raised */ - member function do_execute(self in out nocopy ut_executable, a_item in out nocopy ut_suite_item) return boolean, + member function do_execute( + self in out nocopy ut_executable, + a_item in out nocopy ut_suite_item, + a_ignored_exception_names in ut_varchar2_rows := null, + a_ignored_exception_numbers in ut_varchar2_rows :=null + ) return boolean, member function get_error_stack_trace return varchar2 ) not final / diff --git a/source/core/types/ut_executable_test.tpb b/source/core/types/ut_executable_test.tpb index c5a7f29a1..8e48bd70a 100644 --- a/source/core/types/ut_executable_test.tpb +++ b/source/core/types/ut_executable_test.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_executable_test as +create or replace noneditionable type body ut_executable_test as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -31,141 +31,129 @@ create or replace type body ut_executable_test as member procedure do_execute( self in out nocopy ut_executable_test, a_item in out nocopy ut_suite_item, - a_expected_error_codes in ut_varchar2_rows + a_expected_errors in ut_varchar2_rows ) is l_completed_without_errors boolean; begin - l_completed_without_errors := self.do_execute(a_item, a_expected_error_codes); + l_completed_without_errors := self.do_execute(a_item, a_expected_errors); end do_execute; member function do_execute( self in out nocopy ut_executable_test, a_item in out nocopy ut_suite_item, - a_expected_error_codes in ut_varchar2_rows + a_expected_errors in ut_varchar2_rows ) return boolean is - l_expected_except_message varchar2(4000); - l_expected_error_numbers ut_integer_list; - - function build_exception_numbers_list( - a_item in out nocopy ut_suite_item, - a_expected_error_codes in ut_varchar2_rows - ) return ut_integer_list is + l_failure_message varchar2(32767); + l_expected_error_names ut_varchar2_rows; + l_expected_error_numbers ut_varchar2_rows; + l_all_expected_errors ut_varchar2_rows; + + procedure build_expected_exceptions( + a_item in out nocopy ut_suite_item, + a_exception_definitions in ut_varchar2_rows, + a_exception_names_list out ut_varchar2_rows, + a_exception_numbers_list out ut_varchar2_rows + ) is l_exception_number integer; - l_exception_number_list ut_integer_list := ut_integer_list(); - c_regexp_for_exception_no constant varchar2(30) := '^-?[[:digit:]]{1,5}$'; - - c_integer_exception constant varchar2(1) := 'I'; - c_named_exception constant varchar2(1) := 'N'; - - function is_valid_qualified_name (a_name varchar2) return boolean is - l_name varchar2(500); - begin - l_name := dbms_assert.qualified_sql_name(a_name); - return true; - exception when others then - return false; - end; - - function check_exception_type(a_exception_name in varchar2) return varchar2 is - l_exception_type varchar2(50); - begin - --check if it is a predefined exception + l_exception_name varchar2(128); + function to_exception_number (a_exception_var in varchar2, a_item ut_suite_item) return integer is + function get_exception_no(a_exception_var in varchar2) return integer is + l_exc_no integer; begin - execute immediate 'begin null; exception when '||a_exception_name||' then null; end;'; - l_exception_type := c_named_exception; + execute immediate 'declare l_exception integer; begin :l_exception := '||a_exception_var||'; end;' + using out l_exc_no; + return l_exc_no; exception when others then - if dbms_utility.format_error_stack() like '%PLS-00485%' then - declare - e_invalid_number exception; - pragma exception_init ( e_invalid_number, -6502 ); - begin - execute immediate 'declare x integer := '||a_exception_name||'; begin null; end;'; - l_exception_type := c_integer_exception; - exception - when others then - null; - end; - end if; + return null; end; - return l_exception_type; - end; - - function get_exception_number (a_exception_var in varchar2) return integer is - l_exc_no integer; - l_exc_type varchar2(50); function remap_no_data_found (a_number integer) return integer is begin return case a_number when 100 then -1403 else a_number end; end; begin - l_exc_type := check_exception_type(a_exception_var); - - execute immediate - case l_exc_type - when c_integer_exception then - 'declare l_exception number; begin :l_exception := '||a_exception_var||'; end;' - when c_named_exception then - 'begin raise '||a_exception_var||'; exception when others then :l_exception := sqlcode; end;' - else - 'begin :l_exception := null; end;' - end - using out l_exc_no; + return remap_no_data_found( + coalesce( + get_exception_no(a_exception_var), + get_exception_no(a_item.object_name||'.'||a_exception_var), + get_exception_no(a_item.object_owner||'.'||a_exception_var), + get_exception_no(a_item.object_owner||'.'||a_item.object_name||'.'||a_exception_var) + ) + ); + end; + + function to_exception_name (a_exception_var in varchar2, a_item ut_suite_item) return varchar2 is + function verify_name(a_exception_name in varchar2) return varchar2 is + begin + --check if it is an existing, named exception + execute immediate 'begin null; exception when '||a_exception_name||' then null; end;'; + return a_exception_name; + exception + when others then + return null; + end; + begin + return coalesce( + verify_name(a_exception_var), + verify_name(a_item.object_name||'.'||a_exception_var), + verify_name(a_item.object_owner||'.'||a_exception_var), + verify_name(a_item.object_owner||'.'||a_item.object_name||'.'||a_exception_var) + ); - return remap_no_data_found(l_exc_no); end; begin - if a_expected_error_codes is not empty then - for i in 1 .. a_expected_error_codes.count loop - /** - * Check if its a valid qualified name and if so try to resolve name to an exception number - */ - if is_valid_qualified_name(a_expected_error_codes(i)) then - l_exception_number := get_exception_number(a_expected_error_codes(i)); - elsif regexp_like(a_expected_error_codes(i), c_regexp_for_exception_no) then - l_exception_number := a_expected_error_codes(i); - end if; - - if l_exception_number is null then + a_exception_names_list := ut_varchar2_rows(); + a_exception_numbers_list := ut_varchar2_rows(); + if a_exception_definitions is not empty then + for i in 1 .. a_exception_definitions.count loop + l_exception_number := + coalesce( + to_exception_number(a_exception_definitions(i), a_item), + to_number(a_exception_definitions(i) default null on conversion error ) + ); + l_exception_name := to_exception_name(a_exception_definitions(i), a_item); + + if l_exception_number is null and l_exception_name is null then a_item.put_warning( - 'Invalid parameter value "'||a_expected_error_codes(i)||'" for "--%throws" annotation. Parameter ignored.', + 'Invalid parameter value "'||a_exception_definitions(i)||'" for "--%throws" annotation. Parameter ignored.', self.procedure_name, a_item.line_no ); elsif l_exception_number >= 0 then a_item.put_warning( - 'Invalid parameter value "'||a_expected_error_codes(i)||'" for "--%throws" annotation. Exception value must be a negative integer. Parameter ignored.', + 'Invalid parameter value "'||a_exception_definitions(i)||'" for "--%throws" annotation. Exception value must be a negative integer. Parameter ignored.', self.procedure_name, a_item.line_no ); - else - l_exception_number_list.extend; - l_exception_number_list(l_exception_number_list.last) := l_exception_number; + elsif l_exception_number is not null then + a_exception_numbers_list.extend; + a_exception_numbers_list(a_exception_numbers_list.last) := l_exception_number; + elsif l_exception_name is not null then + a_exception_names_list.extend; + a_exception_names_list(a_exception_names_list.last) := l_exception_name; end if; - l_exception_number := null; end loop; end if; - - return l_exception_number_list; end; - function failed_expec_errnum_message(a_expected_error_codes in ut_integer_list) return varchar is + + function get_exception_failure_message(a_expected_errors_list ut_varchar2_rows) return varchar2 is l_actual_error_no integer; - l_expected_error_codes varchar2(4000); + l_expected_errors varchar2(4000); l_fail_message varchar2(4000); begin --Convert the ut_varchar2_list to string to can construct the message - l_expected_error_codes := ut_utils.table_to_clob(a_expected_error_codes, ', '); + l_expected_errors := ut_utils.table_to_clob(a_expected_errors_list, ', '); if self.error_stack is null then - l_fail_message := 'Expected one of exceptions ('||l_expected_error_codes||') but nothing was raised.'; + l_fail_message := 'Expected one of exceptions ('|| l_expected_errors|| ') but nothing was raised.'; else - l_actual_error_no := regexp_substr(self.error_stack, '^[a-zA-Z]{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_actual_error_no := regexp_substr(self.error_stack, '^[[:alpha:]]{3}(-[0-9]+)', subexpression=>1); + if not l_actual_error_no member of a_expected_errors_list 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 - l_fail_message := l_fail_message || 'be one of: ('||l_expected_error_codes||')'; + if cardinality(a_expected_errors_list) > 1 then + l_fail_message := l_fail_message || 'be one of: ('|| l_expected_errors|| ')'; else - l_fail_message := l_fail_message || 'equal: '||l_expected_error_codes; + l_fail_message := l_fail_message || 'equal: '||l_expected_errors; end if; l_fail_message := substr( l_fail_message||chr(10)||self.error_stack||chr(10)||self.error_backtrace, 1, 4000 ); end if; @@ -175,14 +163,16 @@ create or replace type body ut_executable_test as end; begin --Create a ut_executable object and call do_execute after that get the data to know the test's execution result - self.do_execute(a_item); - l_expected_error_numbers := build_exception_numbers_list(a_item, a_expected_error_codes); - if l_expected_error_numbers is not null and l_expected_error_numbers is not empty then - l_expected_except_message := failed_expec_errnum_message( l_expected_error_numbers ); + build_expected_exceptions(a_item, a_expected_errors, l_expected_error_names, l_expected_error_numbers ); + + self.do_execute(a_item, l_expected_error_names, l_expected_error_numbers); + l_all_expected_errors := l_expected_error_names multiset union all l_expected_error_numbers; + if l_all_expected_errors is not empty and self.ignored_exception_detected = 0 then + l_failure_message := get_exception_failure_message( l_all_expected_errors); - if l_expected_except_message is not null then + if l_failure_message is not null then ut_expectation_processor.add_expectation_result( - ut_expectation_result(ut_utils.gc_failure, null, l_expected_except_message, false) + ut_expectation_result(ut_utils.gc_failure, null, l_failure_message, false) ); end if; self.error_stack := null; diff --git a/source/core/types/ut_executable_test.tps b/source/core/types/ut_executable_test.tps index 42b6080df..d477456f6 100644 --- a/source/core/types/ut_executable_test.tps +++ b/source/core/types/ut_executable_test.tps @@ -1,7 +1,7 @@ -create or replace type ut_executable_test authid current_user under ut_executable ( +create or replace noneditionable type ut_executable_test authid current_user under ut_executable ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -22,12 +22,12 @@ create or replace type ut_executable_test authid current_user under ut_executabl member procedure do_execute( self in out nocopy ut_executable_test, a_item in out nocopy ut_suite_item, - a_expected_error_codes in ut_varchar2_rows + a_expected_errors in ut_varchar2_rows ), member function do_execute( self in out nocopy ut_executable_test, a_item in out nocopy ut_suite_item, - a_expected_error_codes in ut_varchar2_rows + a_expected_errors in ut_varchar2_rows ) return boolean ) final; diff --git a/source/core/types/ut_executables.tps b/source/core/types/ut_executables.tps index d6d52b3d2..7718c44b8 100644 --- a/source/core/types/ut_executables.tps +++ b/source/core/types/ut_executables.tps @@ -1,7 +1,7 @@ -create or replace type ut_executables as +create or replace noneditionable type ut_executables as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_expectation_result.tpb b/source/core/types/ut_expectation_result.tpb index 0b3adf983..3ffd11789 100644 --- a/source/core/types/ut_expectation_result.tpb +++ b/source/core/types/ut_expectation_result.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_expectation_result is +create or replace noneditionable type body ut_expectation_result is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_expectation_result.tps b/source/core/types/ut_expectation_result.tps index d4b4c7548..e7eedd5ce 100644 --- a/source/core/types/ut_expectation_result.tps +++ b/source/core/types/ut_expectation_result.tps @@ -1,7 +1,7 @@ -create or replace type ut_expectation_result under ut_event_item( +create or replace noneditionable type ut_expectation_result under ut_event_item( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_expectation_results.tps b/source/core/types/ut_expectation_results.tps index e5e4c6223..168941112 100644 --- a/source/core/types/ut_expectation_results.tps +++ b/source/core/types/ut_expectation_results.tps @@ -1,7 +1,7 @@ -create or replace type ut_expectation_results as +create or replace noneditionable type ut_expectation_results as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_file_mapping.tpb b/source/core/types/ut_file_mapping.tpb index dab1e35c5..d693ac633 100644 --- a/source/core/types/ut_file_mapping.tpb +++ b/source/core/types/ut_file_mapping.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_file_mapping as +create or replace noneditionable type body ut_file_mapping as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_file_mapping.tps b/source/core/types/ut_file_mapping.tps index 941d95283..d28ef40ae 100644 --- a/source/core/types/ut_file_mapping.tps +++ b/source/core/types/ut_file_mapping.tps @@ -1,7 +1,7 @@ -create or replace type ut_file_mapping as object( +create or replace noneditionable type ut_file_mapping as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_file_mappings.tps b/source/core/types/ut_file_mappings.tps index 240fb8dd7..715530ee0 100644 --- a/source/core/types/ut_file_mappings.tps +++ b/source/core/types/ut_file_mappings.tps @@ -1,7 +1,7 @@ -create or replace type ut_file_mappings as +create or replace noneditionable type ut_file_mappings as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_integer_list.tps b/source/core/types/ut_integer_list.tps index ec9a2a78f..b15cf743c 100644 --- a/source/core/types/ut_integer_list.tps +++ b/source/core/types/ut_integer_list.tps @@ -1,7 +1,7 @@ -create or replace type ut_integer_list as +create or replace noneditionable type ut_integer_list as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_key_value_pair.tps b/source/core/types/ut_key_value_pair.tps index 5a337e608..e26fff031 100644 --- a/source/core/types/ut_key_value_pair.tps +++ b/source/core/types/ut_key_value_pair.tps @@ -1,7 +1,7 @@ -create or replace type ut_key_value_pair force as object( +create or replace noneditionable type ut_key_value_pair force as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_key_value_pairs.tps b/source/core/types/ut_key_value_pairs.tps index 24bdded36..d9e829dc9 100644 --- a/source/core/types/ut_key_value_pairs.tps +++ b/source/core/types/ut_key_value_pairs.tps @@ -1,7 +1,7 @@ -create or replace type ut_key_value_pairs as +create or replace noneditionable type ut_key_value_pairs as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_logical_suite.tpb b/source/core/types/ut_logical_suite.tpb index 036d40d51..c91320d8a 100644 --- a/source/core/types/ut_logical_suite.tpb +++ b/source/core/types/ut_logical_suite.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_logical_suite as +create or replace noneditionable type body ut_logical_suite as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_logical_suite.tps b/source/core/types/ut_logical_suite.tps index c4f2f699b..245c422a1 100644 --- a/source/core/types/ut_logical_suite.tps +++ b/source/core/types/ut_logical_suite.tps @@ -1,7 +1,7 @@ -create or replace type ut_logical_suite force under ut_suite_item ( +create or replace noneditionable type ut_logical_suite force under ut_suite_item ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_object_name.tpb b/source/core/types/ut_object_name.tpb index 76acc15bf..67834a9ed 100644 --- a/source/core/types/ut_object_name.tpb +++ b/source/core/types/ut_object_name.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_object_name as +create or replace noneditionable type body ut_object_name as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_object_name.tps b/source/core/types/ut_object_name.tps index 5f8f724c4..7f6341a32 100644 --- a/source/core/types/ut_object_name.tps +++ b/source/core/types/ut_object_name.tps @@ -1,7 +1,7 @@ -create or replace type ut_object_name as object ( +create or replace noneditionable type ut_object_name as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_object_names.tps b/source/core/types/ut_object_names.tps index 03830be17..879493a63 100644 --- a/source/core/types/ut_object_names.tps +++ b/source/core/types/ut_object_names.tps @@ -1,7 +1,7 @@ -create or replace type ut_object_names as +create or replace noneditionable type ut_object_names as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_output_reporter_base.tpb b/source/core/types/ut_output_reporter_base.tpb index f6bb27b94..4553f1780 100644 --- a/source/core/types/ut_output_reporter_base.tpb +++ b/source/core/types/ut_output_reporter_base.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_output_reporter_base is +create or replace noneditionable type body ut_output_reporter_base is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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..b9ec4f7cd 100644 --- a/source/core/types/ut_output_reporter_base.tps +++ b/source/core/types/ut_output_reporter_base.tps @@ -1,7 +1,7 @@ -create or replace type ut_output_reporter_base under ut_reporter_base( +create or replace noneditionable type ut_output_reporter_base under ut_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_path_item.tpb b/source/core/types/ut_path_item.tpb index 6cdb2b8ad..486fde865 100644 --- a/source/core/types/ut_path_item.tpb +++ b/source/core/types/ut_path_item.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_path_item as +create or replace noneditionable type body ut_path_item as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_path_item.tps b/source/core/types/ut_path_item.tps index c8ec81be5..fdc1e8ac2 100644 --- a/source/core/types/ut_path_item.tps +++ b/source/core/types/ut_path_item.tps @@ -1,7 +1,7 @@ -create or replace type ut_path_item as object ( +create or replace noneditionable type ut_path_item as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_path_items.tps b/source/core/types/ut_path_items.tps index 0c87cf28c..64f8dd5fe 100644 --- a/source/core/types/ut_path_items.tps +++ b/source/core/types/ut_path_items.tps @@ -1,7 +1,7 @@ -create or replace type ut_path_items as +create or replace noneditionable type ut_path_items as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_reporter_base.tpb b/source/core/types/ut_reporter_base.tpb index 3f8984331..e457645f4 100644 --- a/source/core/types/ut_reporter_base.tpb +++ b/source/core/types/ut_reporter_base.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_reporter_base is +create or replace noneditionable type body ut_reporter_base is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -165,52 +165,54 @@ create or replace type body ut_reporter_base is end; overriding member procedure on_event( self in out nocopy ut_reporter_base, a_event_name varchar2, a_event_item ut_event_item) is + /* Resolves issue with ORA-21779 being thrown see issue: 1309 -> ( https://github.com/utPLSQL/utPLSQL/issues/1309#issuecomment-4020289898 ) */ + l_event_item ut_event_item := a_event_item; begin case a_event_name when ut_event_manager.gc_initialize - then self.on_initialize(treat(a_event_item as ut_run)); + then self.on_initialize(treat(l_event_item as ut_run)); when ut_event_manager.gc_before_run - then self.before_calling_run(treat(a_event_item as ut_run)); + then self.before_calling_run(treat(l_event_item as ut_run)); when ut_event_manager.gc_before_suite - then self.before_calling_suite(treat(a_event_item as ut_logical_suite)); + then self.before_calling_suite(treat(l_event_item as ut_logical_suite)); when ut_event_manager.gc_before_before_all - then self.before_calling_before_all(treat(a_event_item as ut_executable)); + then self.before_calling_before_all(treat(l_event_item as ut_executable)); when ut_event_manager.gc_before_before_each - then self.before_calling_before_each(treat(a_event_item as ut_executable)); + then self.before_calling_before_each(treat(l_event_item as ut_executable)); when ut_event_manager.gc_before_test - then self.before_calling_test(treat(a_event_item as ut_test)); + then self.before_calling_test(treat(l_event_item as ut_test)); when ut_event_manager.gc_before_before_test - then self.before_calling_before_test(treat(a_event_item as ut_executable)); + then self.before_calling_before_test(treat(l_event_item as ut_executable)); when ut_event_manager.gc_before_test_execute - then self.before_calling_test_execute(treat(a_event_item as ut_executable)); + then self.before_calling_test_execute(treat(l_event_item as ut_executable)); when ut_event_manager.gc_before_after_test - then self.before_calling_after_test(treat(a_event_item as ut_executable)); + then self.before_calling_after_test(treat(l_event_item as ut_executable)); when ut_event_manager.gc_before_after_each - then self.before_calling_after_each(treat(a_event_item as ut_executable)); + then self.before_calling_after_each(treat(l_event_item as ut_executable)); when ut_event_manager.gc_before_after_all - then self.before_calling_after_all(treat(a_event_item as ut_executable)); + then self.before_calling_after_all(treat(l_event_item as ut_executable)); when ut_event_manager.gc_after_run - then self.after_calling_run(treat(a_event_item as ut_run)); + then self.after_calling_run(treat(l_event_item as ut_run)); when ut_event_manager.gc_after_suite - then self.after_calling_suite(treat(a_event_item as ut_logical_suite)); + then self.after_calling_suite(treat(l_event_item as ut_logical_suite)); when ut_event_manager.gc_after_before_all - then self.after_calling_before_all(treat(a_event_item as ut_executable)); + then self.after_calling_before_all(treat(l_event_item as ut_executable)); when ut_event_manager.gc_after_before_each - then self.after_calling_before_each(treat(a_event_item as ut_executable)); + then self.after_calling_before_each(treat(l_event_item as ut_executable)); when ut_event_manager.gc_after_test - then self.after_calling_test(treat(a_event_item as ut_test)); + then self.after_calling_test(treat(l_event_item as ut_test)); when ut_event_manager.gc_after_before_test - then self.after_calling_before_test(treat(a_event_item as ut_executable)); + then self.after_calling_before_test(treat(l_event_item as ut_executable)); when ut_event_manager.gc_after_test_execute - then self.after_calling_test_execute(treat(a_event_item as ut_executable)); + then self.after_calling_test_execute(treat(l_event_item as ut_executable)); when ut_event_manager.gc_after_after_test - then self.after_calling_after_test(treat(a_event_item as ut_executable)); + then self.after_calling_after_test(treat(l_event_item as ut_executable)); when ut_event_manager.gc_after_after_each - then self.after_calling_after_each(treat(a_event_item as ut_executable)); + then self.after_calling_after_each(treat(l_event_item as ut_executable)); when ut_event_manager.gc_after_after_all - then self.after_calling_after_all(treat(a_event_item as ut_executable)); + then self.after_calling_after_all(treat(l_event_item as ut_executable)); when ut_event_manager.gc_finalize - then self.on_finalize(treat(a_event_item as ut_run)); + then self.on_finalize(treat(l_event_item as ut_run)); else null; end case; end; diff --git a/source/core/types/ut_reporter_base.tps b/source/core/types/ut_reporter_base.tps index de157136e..af0472dc5 100644 --- a/source/core/types/ut_reporter_base.tps +++ b/source/core/types/ut_reporter_base.tps @@ -1,7 +1,7 @@ -create or replace type ut_reporter_base under ut_event_listener ( +create or replace noneditionable type ut_reporter_base under ut_event_listener ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_reporter_info.tps b/source/core/types/ut_reporter_info.tps index f09c42e07..29c7559f1 100644 --- a/source/core/types/ut_reporter_info.tps +++ b/source/core/types/ut_reporter_info.tps @@ -1,7 +1,7 @@ -create or replace type ut_reporter_info as object ( +create or replace noneditionable type ut_reporter_info as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_reporters.tps b/source/core/types/ut_reporters.tps index a2802bb26..fba22480f 100644 --- a/source/core/types/ut_reporters.tps +++ b/source/core/types/ut_reporters.tps @@ -1,7 +1,7 @@ -create or replace type ut_reporters as +create or replace noneditionable type ut_reporters as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_reporters_info.tps b/source/core/types/ut_reporters_info.tps index 82c264f36..4f6f054a3 100644 --- a/source/core/types/ut_reporters_info.tps +++ b/source/core/types/ut_reporters_info.tps @@ -1,7 +1,7 @@ -create or replace type ut_reporters_info as +create or replace noneditionable type ut_reporters_info as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_results_counter.tpb b/source/core/types/ut_results_counter.tpb index 811389e0e..54f9e0b48 100644 --- a/source/core/types/ut_results_counter.tpb +++ b/source/core/types/ut_results_counter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_results_counter as +create or replace noneditionable type body ut_results_counter as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_results_counter.tps b/source/core/types/ut_results_counter.tps index 913ae0967..477d4b217 100644 --- a/source/core/types/ut_results_counter.tps +++ b/source/core/types/ut_results_counter.tps @@ -1,7 +1,7 @@ -create or replace type ut_results_counter as object( +create or replace noneditionable type ut_results_counter as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_run.tpb b/source/core/types/ut_run.tpb index cdafb30fc..80621b5ad 100644 --- a/source/core/types/ut_run.tpb +++ b/source/core/types/ut_run.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_run as +create or replace noneditionable type body ut_run as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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..551e56ac4 100644 --- a/source/core/types/ut_run.tps +++ b/source/core/types/ut_run.tps @@ -1,7 +1,7 @@ -create or replace type ut_run under ut_suite_item ( +create or replace noneditionable type ut_run under ut_suite_item ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_run_info.tpb b/source/core/types/ut_run_info.tpb index 6edc0f109..3de74b2b4 100644 --- a/source/core/types/ut_run_info.tpb +++ b/source/core/types/ut_run_info.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_run_info as +create or replace noneditionable type body ut_run_info as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_run_info.tps b/source/core/types/ut_run_info.tps index 4a3da691f..00919b172 100644 --- a/source/core/types/ut_run_info.tps +++ b/source/core/types/ut_run_info.tps @@ -1,7 +1,7 @@ -create or replace type ut_run_info under ut_event_item ( +create or replace noneditionable type ut_run_info under ut_event_item ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_stack.tpb b/source/core/types/ut_stack.tpb new file mode 100644 index 000000000..4298ae35c --- /dev/null +++ b/source/core/types/ut_stack.tpb @@ -0,0 +1,58 @@ +create or replace noneditionable type body ut_stack as + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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/reporters/ut_coveralls_reporter.tps b/source/core/types/ut_stack.tps similarity index 51% rename from source/reporters/ut_coveralls_reporter.tps rename to source/core/types/ut_stack.tps index 74363b751..ed79bea55 100644 --- a/source/reporters/ut_coveralls_reporter.tps +++ b/source/core/types/ut_stack.tps @@ -1,7 +1,9 @@ -create or replace type ut_coveralls_reporter under ut_coverage_reporter_base( +create or replace noneditionable type ut_stack as object ( + top integer, + tokens ut_varchar2_list, /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -15,17 +17,10 @@ create or replace type ut_coveralls_reporter under ut_coverage_reporter_base( See the License for the specific language governing permissions and limitations under the License. */ - - /** - * Builds a coverage XML report that follows rules described in - * https://docs.coveralls.io/api-introduction - */ - constructor function ut_coveralls_reporter( - self in out nocopy ut_coveralls_reporter - ) return self as result, - - overriding member procedure after_calling_run(self in out nocopy ut_coveralls_reporter, a_run in ut_run), - - overriding member function get_description return varchar2 + 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/types/ut_suite.tpb b/source/core/types/ut_suite.tpb index e3a4687d8..b4e3eabf0 100644 --- a/source/core/types/ut_suite.tpb +++ b/source/core/types/ut_suite.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_suite as +create or replace noneditionable type body ut_suite as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_suite.tps b/source/core/types/ut_suite.tps index ff7f3e171..edce778f1 100644 --- a/source/core/types/ut_suite.tps +++ b/source/core/types/ut_suite.tps @@ -1,7 +1,7 @@ -create or replace type ut_suite under ut_logical_suite ( +create or replace noneditionable type ut_suite under ut_logical_suite ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_suite_cache_row.tps b/source/core/types/ut_suite_cache_row.tps index c147a8757..1ee371a88 100644 --- a/source/core/types/ut_suite_cache_row.tps +++ b/source/core/types/ut_suite_cache_row.tps @@ -1,7 +1,7 @@ -create or replace type ut_suite_cache_row as object ( +create or replace noneditionable type ut_suite_cache_row as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_suite_cache_rows.tps b/source/core/types/ut_suite_cache_rows.tps index 9b6919df5..84bcc9f75 100644 --- a/source/core/types/ut_suite_cache_rows.tps +++ b/source/core/types/ut_suite_cache_rows.tps @@ -1,7 +1,7 @@ -create type ut_suite_cache_rows as +create or replace noneditionable type ut_suite_cache_rows as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_suite_context.tpb b/source/core/types/ut_suite_context.tpb index be92e6fc9..dad24e42e 100644 --- a/source/core/types/ut_suite_context.tpb +++ b/source/core/types/ut_suite_context.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_suite_context as +create or replace noneditionable type body ut_suite_context as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_suite_context.tps b/source/core/types/ut_suite_context.tps index 5e3d20f87..b890e2633 100644 --- a/source/core/types/ut_suite_context.tps +++ b/source/core/types/ut_suite_context.tps @@ -1,7 +1,7 @@ -create or replace type ut_suite_context under ut_suite ( +create or replace noneditionable type ut_suite_context under ut_suite ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_suite_item.tpb b/source/core/types/ut_suite_item.tpb index 648f10dd1..8e8c6fd90 100644 --- a/source/core/types/ut_suite_item.tpb +++ b/source/core/types/ut_suite_item.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_suite_item as +create or replace noneditionable type body ut_suite_item as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_suite_item.tps b/source/core/types/ut_suite_item.tps index 8184f2a63..2e990353f 100644 --- a/source/core/types/ut_suite_item.tps +++ b/source/core/types/ut_suite_item.tps @@ -1,7 +1,7 @@ -create or replace type ut_suite_item force under ut_event_item ( +create or replace noneditionable type ut_suite_item force under ut_event_item ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_suite_items.tps b/source/core/types/ut_suite_items.tps index 615283446..b7ce9873c 100644 --- a/source/core/types/ut_suite_items.tps +++ b/source/core/types/ut_suite_items.tps @@ -1,7 +1,7 @@ -create or replace type ut_suite_items as +create or replace noneditionable type ut_suite_items as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_test.tpb b/source/core/types/ut_test.tpb index 946f8990d..61a1e39f8 100644 --- a/source/core/types/ut_test.tpb +++ b/source/core/types/ut_test.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_test as +create or replace noneditionable type body ut_test as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_test.tps b/source/core/types/ut_test.tps index dbba64961..cb50c2fd4 100644 --- a/source/core/types/ut_test.tps +++ b/source/core/types/ut_test.tps @@ -1,7 +1,7 @@ -create or replace type ut_test force under ut_suite_item ( +create or replace noneditionable type ut_test force under ut_suite_item ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_varchar2_list.tps b/source/core/types/ut_varchar2_list.tps index f0b5df509..8203a4bab 100644 --- a/source/core/types/ut_varchar2_list.tps +++ b/source/core/types/ut_varchar2_list.tps @@ -1,7 +1,7 @@ -create or replace type ut_varchar2_list as +create or replace noneditionable type ut_varchar2_list as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_varchar2_rows.tps b/source/core/types/ut_varchar2_rows.tps index e7fa29c29..a2e25e9fb 100644 --- a/source/core/types/ut_varchar2_rows.tps +++ b/source/core/types/ut_varchar2_rows.tps @@ -1,7 +1,7 @@ -create or replace type ut_varchar2_rows as +create or replace noneditionable type ut_varchar2_rows as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/ut_dbms_output_cache.sql b/source/core/ut_dbms_output_cache.sql index e61b403c8..802f30942 100644 --- a/source/core/ut_dbms_output_cache.sql +++ b/source/core/ut_dbms_output_cache.sql @@ -1,15 +1,16 @@ /* -utPLSQL - Version 3 -Copyright 2016 - 2021 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. + utPLSQL - Version 3 + Copyright 2016 - 2026 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. */ /* * This table is not a global temporary table as it needs to allow cross-session data exchange diff --git a/source/core/ut_expectation_processor.pkb b/source/core/ut_expectation_processor.pkb index 77b734b30..3fb39a5b9 100644 --- a/source/core/ut_expectation_processor.pkb +++ b/source/core/ut_expectation_processor.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_expectation_processor as +create or replace noneditionable package body ut_expectation_processor as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_expectation_processor.pks b/source/core/ut_expectation_processor.pks index dd9187526..5349ee70a 100644 --- a/source/core/ut_expectation_processor.pks +++ b/source/core/ut_expectation_processor.pks @@ -1,7 +1,7 @@ -create or replace package ut_expectation_processor authid current_user as +create or replace noneditionable package ut_expectation_processor authid current_user as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/ut_file_mapper.pkb b/source/core/ut_file_mapper.pkb index 425360533..40204a5da 100644 --- a/source/core/ut_file_mapper.pkb +++ b/source/core/ut_file_mapper.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_file_mapper is +create or replace noneditionable package body ut_file_mapper is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/ut_file_mapper.pks b/source/core/ut_file_mapper.pks index adc4c983f..1363e81ec 100644 --- a/source/core/ut_file_mapper.pks +++ b/source/core/ut_file_mapper.pks @@ -1,7 +1,7 @@ -create or replace package ut_file_mapper authid current_user is +create or replace noneditionable package ut_file_mapper authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/ut_metadata.pkb b/source/core/ut_metadata.pkb index 700c0efb0..d9939ad45 100644 --- a/source/core/ut_metadata.pkb +++ b/source/core/ut_metadata.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_metadata as +create or replace noneditionable package body ut_metadata as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_metadata.pks b/source/core/ut_metadata.pks index 27bfa5d30..42f136504 100644 --- a/source/core/ut_metadata.pks +++ b/source/core/ut_metadata.pks @@ -1,7 +1,7 @@ -create or replace package ut_metadata authid current_user as +create or replace noneditionable package ut_metadata authid current_user as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/ut_savepoint_seq.sql b/source/core/ut_savepoint_seq.sql index 5b0cda35e..2026ab0e3 100644 --- a/source/core/ut_savepoint_seq.sql +++ b/source/core/ut_savepoint_seq.sql @@ -1,7 +1,7 @@ create sequence ut_savepoint_seq /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/ut_suite_builder.pkb b/source/core/ut_suite_builder.pkb index ebb113370..4f0e28606 100644 --- a/source/core/ut_suite_builder.pkb +++ b/source/core/ut_suite_builder.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_suite_builder is +create or replace noneditionable package body ut_suite_builder is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -754,7 +754,7 @@ create or replace package body ut_suite_builder is l_used_context_names(l_context_name) := true; l_context := ut_suite_context(a_parent.object_owner, a_parent.object_name, l_context_name, l_context_pos ); - l_context.path := a_parent.path||'.'||l_context_name; + l_context.path := a_parent.path||'.'||l_context.name; l_context.description := coalesce( a_annotations.by_line( l_context_pos ).text, l_context_name ); l_context.parse_time := a_annotations.parse_time; diff --git a/source/core/ut_suite_builder.pks b/source/core/ut_suite_builder.pks index 352601a6d..35f207ea4 100644 --- a/source/core/ut_suite_builder.pks +++ b/source/core/ut_suite_builder.pks @@ -1,7 +1,7 @@ -create or replace package ut_suite_builder authid current_user is +create or replace noneditionable package ut_suite_builder authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/ut_suite_cache.sql b/source/core/ut_suite_cache.sql index 182caabd0..1d7389e9f 100644 --- a/source/core/ut_suite_cache.sql +++ b/source/core/ut_suite_cache.sql @@ -1,7 +1,7 @@ create table ut_suite_cache /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index d3e832a9b..9aa3cf63d 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_suite_cache_manager is +create or replace noneditionable package body ut_suite_cache_manager is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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 @@ -367,7 +306,7 @@ create or replace package body ut_suite_cache_manager is l_object_owner varchar2(250) := upper(a_object_owner); l_object_name varchar2(250) := upper(a_object_name); begin - if a_suite_items is not null and a_suite_items.count = 0 then + if a_suite_items is null or a_suite_items.count = 0 then delete from ut_suite_cache t where t.object_owner = l_object_owner @@ -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..6ac1d7bd6 100644 --- a/source/core/ut_suite_cache_manager.pks +++ b/source/core/ut_suite_cache_manager.pks @@ -1,7 +1,7 @@ -create or replace package ut_suite_cache_manager authid definer is +create or replace noneditionable package ut_suite_cache_manager authid definer is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_cache_package.sql b/source/core/ut_suite_cache_package.sql index 7a146ff83..40b7b8dec 100644 --- a/source/core/ut_suite_cache_package.sql +++ b/source/core/ut_suite_cache_package.sql @@ -1,7 +1,7 @@ create table ut_suite_cache_package ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/ut_suite_cache_schema.sql b/source/core/ut_suite_cache_schema.sql index 636816e65..cace332e3 100644 --- a/source/core/ut_suite_cache_schema.sql +++ b/source/core/ut_suite_cache_schema.sql @@ -1,7 +1,7 @@ create table ut_suite_cache_schema ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/ut_suite_cache_seq.sql b/source/core/ut_suite_cache_seq.sql index e1e560286..020c25c4d 100644 --- a/source/core/ut_suite_cache_seq.sql +++ b/source/core/ut_suite_cache_seq.sql @@ -1,7 +1,7 @@ create sequence ut_suite_cache_seq /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/core/ut_suite_manager.pkb b/source/core/ut_suite_manager.pkb index 805fd608b..fa60be59e 100644 --- a/source/core/ut_suite_manager.pkb +++ b/source/core/ut_suite_manager.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_suite_manager is +create or replace noneditionable package body ut_suite_manager is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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..60c3595bb 100644 --- a/source/core/ut_suite_manager.pks +++ b/source/core/ut_suite_manager.pks @@ -1,7 +1,7 @@ -create or replace package ut_suite_manager authid current_user is +create or replace noneditionable package ut_suite_manager authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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..447ba47d5 --- /dev/null +++ b/source/core/ut_suite_tag_filter.pkb @@ -0,0 +1,290 @@ +create or replace noneditionable package body ut_suite_tag_filter is + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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_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)),'tags'); + 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_results ut_suite_cache_rows; + l_sql varchar2(32000); + l_tags_filter_clause 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 tags + from table(:suite_items) c + ), + propagate_tags_from_ancestors as ( + select c.id, c.self_type, c.path, c.object_owner, cast(collect(c_tags.tag) as ut_varchar2_rows) as tags + from suites_mv c + left join suites_mv t + on t.self_type in ('UT_SUITE','UT_SUITE_CONTEXT') + and c.path like t.path||'.%' /* all descendants */ + and c.object_owner = t.object_owner + outer apply (select column_value tag from table(c.tags) union select column_value tag from table(t.tags) ) c_tags + group by c.id, c.self_type, c.path, c.object_owner + ), + filter_by_tags as ( + select * + from propagate_tags_from_ancestors x + where ( ]'||l_tags_filter_clause||q'[ ) + ), + only_items_with_tests as ( + select item.obj + from suites_mv item + where exists ( + select 1 + from filter_by_tags tst + where tst.self_type in ('UT_TEST') + and (tst.path||'.' like item.path || '.%' /*all descendants and self*/ and item.object_owner = tst.object_owner) + ) + ) + select obj from only_items_with_tests]'; + execute immediate l_sql bulk collect into l_results using a_suite_items; + + return l_results; + 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..bb3d4ad73 --- /dev/null +++ b/source/core/ut_suite_tag_filter.pks @@ -0,0 +1,55 @@ +create or replace noneditionable package ut_suite_tag_filter authid definer is + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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..c8ced3bb9 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_utils is +create or replace noneditionable package body ut_utils is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -19,10 +19,13 @@ 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); + gc_open_chars constant varchar2(4):= '[{(<'; + gc_close_chars constant varchar2(4):= ']})>'; + gc_max_plsql_source_len constant integer := 32767; function surround_with(a_value varchar2, a_quote_char varchar2) return varchar2 is begin @@ -91,8 +94,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 +114,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 +137,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 +414,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 +470,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 +573,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; @@ -656,94 +660,234 @@ create or replace package body ut_utils is return l_result; end; - function replace_multiline_comments(a_source clob) return clob is - l_result clob; - l_ml_comment_start binary_integer := 1; - l_comment_start binary_integer := 1; - l_text_start binary_integer := 1; - l_escaped_text_start binary_integer := 1; - l_escaped_text_end_char varchar2(1 char); - l_end binary_integer := 1; - l_ml_comment clob; - l_newlines_count binary_integer; - l_offset binary_integer := 1; - l_length binary_integer := coalesce(dbms_lob.getlength(a_source), 0); - begin - l_ml_comment_start := instr(a_source,'/*'); - l_comment_start := instr(a_source,'--'); - l_text_start := instr(a_source,''''); - l_escaped_text_start := instr(a_source,q'[q']'); - while l_offset > 0 and l_ml_comment_start > 0 loop - - if l_ml_comment_start > 0 and (l_ml_comment_start < l_comment_start or l_comment_start = 0) - and (l_ml_comment_start < l_text_start or l_text_start = 0)and (l_ml_comment_start < l_escaped_text_start or l_escaped_text_start = 0) - then - l_end := instr(a_source,'*/',l_ml_comment_start+2); - append_to_clob(l_result, dbms_lob.substr(a_source, l_ml_comment_start-l_offset, l_offset)); + function scan_line(a_line in varchar2, a_in_ml_comment in out boolean) return varchar2 is + -- Normal scan + l_remaining varchar2(32767) := a_line; + l_line varchar2(32767); + l_ml_start binary_integer; + l_comment_start binary_integer; + l_text_start binary_integer; + l_eq_text_start binary_integer; + l_eq_end_char varchar2(1 char); + l_pos binary_integer; + l_end binary_integer; + l_ml_end binary_integer; + begin + if a_in_ml_comment then + l_ml_end := instr(l_remaining, '*/'); + if l_ml_end > 0 then + a_in_ml_comment := false; + l_remaining := substr(l_remaining, l_ml_end + 2); + else + return null; + end if; + end if; + + loop + exit when l_remaining is null; + l_ml_start := instr(l_remaining, '/*'); + l_comment_start := instr(l_remaining, '--'); + l_text_start := instr(l_remaining, ''''); + -- q' always puts ' at l_text_start; just check the char immediately before it + l_eq_text_start := case + when l_text_start > 1 and substr(l_remaining, l_text_start - 1, 1) = 'q' + then l_text_start - 1 + else 0 + end; + -- Sentinel gc_max_plsql_source_len means "not present"; 32767 is beyond any VARCHAR2 position + l_pos := least( + case when l_ml_start > 0 then l_ml_start else gc_max_plsql_source_len end, + case when l_comment_start > 0 then l_comment_start else gc_max_plsql_source_len end, + case when l_text_start > 0 then l_text_start else gc_max_plsql_source_len end, + case when l_eq_text_start > 0 then l_eq_text_start else gc_max_plsql_source_len end + ); + + if l_pos = gc_max_plsql_source_len then + l_line := l_line || l_remaining; + exit; + end if; + + l_line := l_line || substr(l_remaining, 1, l_pos - 1); + l_remaining := substr(l_remaining, l_pos); + -- l_remaining now starts exactly at the token; all branch offsets below are relative to 1 + if l_pos = l_eq_text_start then + -- q-quoted string: l_remaining starts at 'q', delimiter is at position 3 + l_eq_end_char := translate(substr(l_remaining, 3, 1), gc_open_chars, gc_close_chars); + l_end := instr(l_remaining, l_eq_end_char || '''', 4); if l_end > 0 then - l_ml_comment := substr(a_source, l_ml_comment_start, l_end-l_ml_comment_start); - l_newlines_count := length( l_ml_comment ) - length( translate( l_ml_comment, 'a'||chr(10), 'a') ); - if l_newlines_count > 0 then - append_to_clob(l_result, lpad( chr(10), l_newlines_count, chr(10) ) ); - end if; - l_end := l_end + 2; + l_line := l_line || substr(l_remaining, 1, l_end + 1); + l_remaining := substr(l_remaining, l_end + 2); + else + l_line := l_line || l_remaining; + exit; end if; - else - if l_comment_start > 0 and (l_comment_start < l_ml_comment_start or l_ml_comment_start = 0) - and (l_comment_start < l_text_start or l_text_start = 0) and (l_comment_start < l_escaped_text_start or l_escaped_text_start = 0) - then - l_end := instr(a_source,chr(10),l_comment_start+2); - if l_end > 0 then - l_end := l_end + 1; - end if; - elsif l_text_start > 0 and (l_text_start < l_ml_comment_start or l_ml_comment_start = 0) - and (l_text_start < l_comment_start or l_comment_start = 0) and (l_text_start < l_escaped_text_start or l_escaped_text_start = 0) - then - l_end := instr(a_source,q'[']',l_text_start+1); - - --skip double quotes while searching for end of quoted text - while l_end > 0 and l_end = instr(a_source,q'['']',l_text_start+1) loop - l_end := instr(a_source,q'[']',l_end+1); - end loop; - if l_end > 0 then - l_end := l_end + 1; + elsif l_pos = l_ml_start then + -- Multi-line comment: l_remaining starts at '/*', so end search starts at 3 + l_ml_end := instr(l_remaining, '*/', 3); + if l_ml_end > 0 then + l_remaining := substr(l_remaining, l_ml_end + 2); + else + a_in_ml_comment := true; + -- preserve trailing newline if present — it belongs to this line, not the comment + if substr(l_remaining, -1) = chr(10) then + l_line := l_line || chr(10); end if; + return l_line; + end if; - elsif l_escaped_text_start > 0 and (l_escaped_text_start < l_ml_comment_start or l_ml_comment_start = 0) - and (l_escaped_text_start < l_comment_start or l_comment_start = 0) and (l_escaped_text_start < l_text_start or l_text_start = 0) - then - --translate char "[" from the start of quoted text "q'[someting]'" into "]" - l_escaped_text_end_char := translate( substr(a_source, l_escaped_text_start + 2, 1), '[{(<', ']})>'); - l_end := instr(a_source,l_escaped_text_end_char||'''',l_escaped_text_start + 3 ); - if l_end > 0 then - l_end := l_end + 2; + elsif l_pos = l_comment_start then + -- Single-line comment: everything from here is comment, keep and stop + l_line := l_line || l_remaining; + return l_line; + + else + -- Regular string literal: l_remaining starts at the opening quote + -- scan from position 2 to skip the opening quote + l_end := 2; + loop + l_end := instr(l_remaining, '''', l_end); + exit when l_end = 0; + if substr(l_remaining, l_end, 2) = '''''' then + l_end := l_end + 2; -- skip escaped quote pair + else + exit; -- real closing quote end if; - end if; + end loop; - if l_end = 0 then - append_to_clob(l_result, substr(a_source, l_offset, l_length-l_offset)); + if l_end > 0 then + l_line := l_line || substr(l_remaining, 1, l_end); + l_remaining := substr(l_remaining, l_end + 1); else - append_to_clob(l_result, substr(a_source, l_offset, l_end-l_offset)); + l_line := l_line || l_remaining; + exit; end if; + end if; - l_offset := l_end; - if l_offset >= l_ml_comment_start then - l_ml_comment_start := instr(a_source,'/*',l_offset); + end loop; + + return l_line; + end; + + function replace_multiline_comments(a_source dbms_preprocessor.source_lines_t) + return dbms_preprocessor.source_lines_t + is + l_result dbms_preprocessor.source_lines_t; + l_line varchar2(32767); + l_remaining varchar2(32767); + l_in_ml_comment boolean := false; + l_ml_end binary_integer; + l_has_ml_comment boolean := false; + begin + if a_source.count = 0 then + return a_source; + end if; + + -- Fast pre-scan to check for presence of multi-line comments; if none, return original source unmodified + for i in 1 .. a_source.count loop + if instr(a_source(i), '/*') > 0 then + l_has_ml_comment := true; + exit; end if; - if l_offset >= l_comment_start then - l_comment_start := instr(a_source,'--',l_offset); + end loop; + + if not l_has_ml_comment then + return a_source; + end if; + + for i in 1 .. a_source.count loop + l_line := a_source(i); + + -- Fast path: inside multi-line comment + if l_in_ml_comment then + l_ml_end := instr(l_line, '*/'); + if l_ml_end > 0 then + l_in_ml_comment := false; + l_line := substr(l_line, l_ml_end + 2); + -- fall through to normal scan + else + l_result(i) := ''; + continue; + end if; end if; - if l_offset >= l_text_start then - l_text_start := instr(a_source,'''',l_offset); + + -- Fast path: no special tokens on this line + if instr(l_line, '/') = 0 + and instr(l_line, '-') = 0 + and instr(l_line, '''') = 0 + then + l_result(i) := l_line; + continue; end if; - if l_offset >= l_escaped_text_start then - l_escaped_text_start := instr(a_source,q'[q']',l_offset); + + -- Normal scan + l_remaining := l_line; + l_line := scan_line(l_remaining, l_in_ml_comment); + if l_line is null then + l_line := ''; end if; + l_result(i) := l_line; end loop; - append_to_clob(l_result, substr(a_source, l_end)); + return l_result; - end; + end replace_multiline_comments; + + function strip_create_header_lines(a_source dbms_preprocessor.source_lines_t) + return dbms_preprocessor.source_lines_t + is + l_result dbms_preprocessor.source_lines_t := a_source; + l_rebased dbms_preprocessor.source_lines_t; + l_create_line pls_integer; + l_header_line pls_integer; + l_header_pos pls_integer := 0; + begin + if l_result.count = 0 then + return l_result; + end if; + + -- remove comment lines that contain "-- create or replace" and find first CREATE + for i in 1..l_result.count loop + l_result(i) := regexp_replace(l_result(i), '^.*[-]{2,}\s*create(\s+or\s+replace).*$', null, 1, 1, 'i'); + if l_create_line is null and regexp_like(l_result(i), '(^|[[:space:]])create([[:space:]]|$)', 'i') then + l_create_line := i; + end if; + end loop; + + -- find first occurrence of object keyword after CREATE (may be on later line) + if l_create_line is not null then + for i in l_create_line..l_result.count loop + l_header_pos := regexp_instr( + l_result(i), + '(^|[[:space:]])(package|type|procedure|function)([[:space:]]|$)', + 1, 1, 0, 'i', 2 + ); + if l_header_pos > 0 then + l_header_line := i; + exit; + end if; + end loop; + + if l_header_line is not null then + -- keep from keyword onward on the header line + l_result(l_header_line) := substr(l_result(l_header_line), l_header_pos); + -- remove "OWNER." from create or replace statement. + -- Owner is not supported along with AUTHID - see issue https://github.com/utPLSQL/utPLSQL/issues/1088 + l_result(l_header_line) := regexp_replace( + l_result(l_header_line), + '^(package|type|procedure|function)\s+("?[[:alpha:]][[:alnum:]$#_]*"?\.)(.*)', + '\1 \3', 1, 1, 'ni' + ); + + -- rebase so header line becomes line 1 (matches preprocessor expectations) + for i in l_header_line .. l_result.count loop + l_rebased(i - l_header_line + 1) := l_result(i); + end loop; + return l_rebased; + end if; + end if; + + return l_result; + end strip_create_header_lines; function get_child_reporters(a_for_reporters ut_reporters_info := null) return ut_reporters_info is l_for_reporters ut_reporters_info := a_for_reporters; @@ -880,13 +1024,13 @@ create or replace package body ut_utils is return regexp_replace(a_item,a_prefix||a_connector); end; - function get_hash(a_data raw, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash is + function get_hash(a_data raw, a_hash_type binary_integer := dbms_crypto.hash_sh256) return t_hash is begin --We cannot run hash on null return case when a_data is null then null else dbms_crypto.hash(a_data, a_hash_type) end; end; - function get_hash(a_data clob, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash is + function get_hash(a_data clob, a_hash_type binary_integer := dbms_crypto.hash_sh256) return t_hash is begin --We cannot run hash on null return case when a_data is null then null else dbms_crypto.hash(a_data, a_hash_type) end; @@ -990,5 +1134,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..46c28f0f0 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -1,7 +1,7 @@ -create or replace package ut_utils authid definer is +create or replace noneditionable package ut_utils authid definer is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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.2.4.4532-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; @@ -401,7 +405,15 @@ create or replace package ut_utils authid definer is /** * Replaces multi-line comments in given source-code with empty lines */ - function replace_multiline_comments(a_source clob) return clob; + function replace_multiline_comments(a_source dbms_preprocessor.source_lines_t) + return dbms_preprocessor.source_lines_t; + + /** + * Strips the CREATE header (possibly split across lines) so source starts at + * package/type/procedure/function keyword, preserving line numbers. + */ + function strip_create_header_lines(a_source dbms_preprocessor.source_lines_t) + return dbms_preprocessor.source_lines_t; /** * Returns list of sub-type reporters for given list of super-type reporters @@ -445,12 +457,12 @@ create or replace package ut_utils authid definer is /* * Wrapper function for calling dbms_crypto.hash */ - function get_hash(a_data raw, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash; + function get_hash(a_data raw, a_hash_type binary_integer := dbms_crypto.hash_sh256) return t_hash; /* * Wrapper function for calling dbms_crypto.hash */ - function get_hash(a_data clob, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash; + function get_hash(a_data clob, a_hash_type binary_integer := dbms_crypto.hash_sh256) return t_hash; /* * Returns a hash value of suitepath based on input path and random seed @@ -472,6 +484,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..9318a7220 100644 --- a/source/create_grants.sql +++ b/source/create_grants.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -104,10 +104,10 @@ grant execute on &&ut3_owner..ut_tfs_junit_reporter to &ut3_user; grant execute on &&ut3_owner..ut_documentation_reporter to &ut3_user; grant execute on &&ut3_owner..ut_sonar_test_reporter to &ut3_user; grant execute on &&ut3_owner..ut_realtime_reporter to &ut3_user; +grant execute on &&ut3_owner..ut_tap_reporter to &ut3_user; --reporters - coverage grant execute on &&ut3_owner..ut_coverage_html_reporter to &ut3_user; grant execute on &&ut3_owner..ut_coverage_sonar_reporter to &ut3_user; -grant execute on &&ut3_owner..ut_coveralls_reporter to &ut3_user; grant execute on &&ut3_owner..ut_coverage_cobertura_reporter to &ut3_user; --reporters - debug grant execute on &&ut3_owner..ut_debug_reporter to &ut3_user; @@ -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_synonyms.sql b/source/create_synonyms.sql index fa1c1dc45..ab338f4f7 100644 --- a/source/create_synonyms.sql +++ b/source/create_synonyms.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -36,10 +36,10 @@ set termout off spool params.sql.tmp select case - when upper('&&ut3_user') = 'PUBLIC' then q'[define action_type='or replace public' + when upper('&&ut3_user') = 'PUBLIC' then q'[define action_type='or replace noneditionable public' ]'||q'[define ut3_user='' ]'||q'[define grantee='PUBLIC']' - else q'[define action_type='or replace' + else q'[define action_type='or replace noneditionable' ]'||q'[define grantee='&&ut3_user'] ]'||q'[define ut3_user='&&ut3_user..']' end @@ -119,10 +119,10 @@ create &action_type. synonym &ut3_user.ut_tfs_junit_reporter for &&ut3_owner..ut create &action_type. synonym &ut3_user.ut_documentation_reporter for &&ut3_owner..ut_documentation_reporter; create &action_type. synonym &ut3_user.ut_sonar_test_reporter for &&ut3_owner..ut_sonar_test_reporter; create &action_type. synonym &ut3_user.ut_realtime_reporter for &&ut3_owner..ut_realtime_reporter; +create &action_type. synonym &ut3_user.ut_tap_reporter for &&ut3_owner..ut_tap_reporter; --reporters - coverage create &action_type. synonym &ut3_user.ut_coverage_html_reporter for &&ut3_owner..ut_coverage_html_reporter; create &action_type. synonym &ut3_user.ut_coverage_sonar_reporter for &&ut3_owner..ut_coverage_sonar_reporter; -create &action_type. synonym &ut3_user.ut_coveralls_reporter for &&ut3_owner..ut_coveralls_reporter; create &action_type. synonym &ut3_user.ut_coverage_cobertura_reporter for &&ut3_owner..ut_coverage_cobertura_reporter; --reporters - debug create &action_type. synonym &ut3_user.ut_debug_reporter for &&ut3_owner..ut_debug_reporter; diff --git a/source/create_synonyms_and_grants_for_public.sql b/source/create_synonyms_and_grants_for_public.sql index 050162d72..25f14217e 100644 --- a/source/create_synonyms_and_grants_for_public.sql +++ b/source/create_synonyms_and_grants_for_public.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/create_user_grants.sql b/source/create_user_grants.sql index 83e594884..c1b81efa4 100644 --- a/source/create_user_grants.sql +++ b/source/create_user_grants.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/create_user_synonyms.sql b/source/create_user_synonyms.sql index a1e7a6acc..7b0e49553 100644 --- a/source/create_user_synonyms.sql +++ b/source/create_user_synonyms.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/create_utplsql_owner.sql b/source/create_utplsql_owner.sql index 1af2cf168..a5b70ffda 100644 --- a/source/create_utplsql_owner.sql +++ b/source/create_utplsql_owner.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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/define_ut3_owner_param.sql b/source/define_ut3_owner_param.sql index 259b49712..192e810b7 100644 --- a/source/define_ut3_owner_param.sql +++ b/source/define_ut3_owner_param.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_compound_data_diff_tmp.sql b/source/expectations/data_values/ut_compound_data_diff_tmp.sql index c10e8d2e8..edd27f766 100644 --- a/source/expectations/data_values/ut_compound_data_diff_tmp.sql +++ b/source/expectations/data_values/ut_compound_data_diff_tmp.sql @@ -1,7 +1,7 @@ create global temporary table ut_compound_data_diff_tmp( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/expectations/data_values/ut_compound_data_helper.pkb b/source/expectations/data_values/ut_compound_data_helper.pkb index 4517cd43c..763c133d7 100644 --- a/source/expectations/data_values/ut_compound_data_helper.pkb +++ b/source/expectations/data_values/ut_compound_data_helper.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_compound_data_helper is +create or replace noneditionable package body ut_compound_data_helper is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_compound_data_helper.pks b/source/expectations/data_values/ut_compound_data_helper.pks index 451cd9bac..6069920c9 100644 --- a/source/expectations/data_values/ut_compound_data_helper.pks +++ b/source/expectations/data_values/ut_compound_data_helper.pks @@ -1,7 +1,7 @@ -create or replace package ut_compound_data_helper authid definer is +create or replace noneditionable package ut_compound_data_helper authid definer is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_compound_data_tmp.sql b/source/expectations/data_values/ut_compound_data_tmp.sql index 1bfa77fca..01cedd97b 100644 --- a/source/expectations/data_values/ut_compound_data_tmp.sql +++ b/source/expectations/data_values/ut_compound_data_tmp.sql @@ -1,7 +1,7 @@ create global temporary table ut_compound_data_tmp( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/expectations/data_values/ut_compound_data_value.tpb b/source/expectations/data_values/ut_compound_data_value.tpb index 2c52ff8fa..7db47827e 100644 --- a/source/expectations/data_values/ut_compound_data_value.tpb +++ b/source/expectations/data_values/ut_compound_data_value.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_compound_data_value as +create or replace noneditionable type body ut_compound_data_value as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_compound_data_value.tps b/source/expectations/data_values/ut_compound_data_value.tps index c14dadceb..c84a59db6 100644 --- a/source/expectations/data_values/ut_compound_data_value.tps +++ b/source/expectations/data_values/ut_compound_data_value.tps @@ -1,7 +1,7 @@ -create or replace type ut_compound_data_value force under ut_data_value( +create or replace noneditionable type ut_compound_data_value force under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_cursor_column.tpb b/source/expectations/data_values/ut_cursor_column.tpb index a9fb6b0f4..3513de23e 100644 --- a/source/expectations/data_values/ut_cursor_column.tpb +++ b/source/expectations/data_values/ut_cursor_column.tpb @@ -1,5 +1,21 @@ -create or replace type body ut_cursor_column as - +create or replace noneditionable type body ut_cursor_column as + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. + */ + member procedure init( self in out nocopy ut_cursor_column, a_col_name varchar2, a_col_schema_name varchar2, diff --git a/source/expectations/data_values/ut_cursor_column.tps b/source/expectations/data_values/ut_cursor_column.tps index 77bf78f01..fcf3fe002 100644 --- a/source/expectations/data_values/ut_cursor_column.tps +++ b/source/expectations/data_values/ut_cursor_column.tps @@ -1,7 +1,7 @@ -create or replace type ut_cursor_column authid current_user as object ( +create or replace noneditionable type ut_cursor_column authid current_user as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_cursor_column_tab.tps b/source/expectations/data_values/ut_cursor_column_tab.tps index 106023d96..169ba3ce4 100644 --- a/source/expectations/data_values/ut_cursor_column_tab.tps +++ b/source/expectations/data_values/ut_cursor_column_tab.tps @@ -1,7 +1,7 @@ -create or replace type ut_cursor_column_tab as +create or replace noneditionable type ut_cursor_column_tab as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_cursor_details.tpb b/source/expectations/data_values/ut_cursor_details.tpb index adc705f65..ff204e3bd 100644 --- a/source/expectations/data_values/ut_cursor_details.tpb +++ b/source/expectations/data_values/ut_cursor_details.tpb @@ -1,4 +1,21 @@ -create or replace type body ut_cursor_details as +create or replace noneditionable type body ut_cursor_details as + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. + */ + member function equals( a_other ut_cursor_details, a_match_options ut_matcher_options ) return boolean is l_diffs integer; @@ -91,7 +108,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/data_values/ut_cursor_details.tps b/source/expectations/data_values/ut_cursor_details.tps index e7f9405eb..c50ed16d3 100644 --- a/source/expectations/data_values/ut_cursor_details.tps +++ b/source/expectations/data_values/ut_cursor_details.tps @@ -1,7 +1,7 @@ -create or replace type ut_cursor_details authid current_user as object ( +create or replace noneditionable type ut_cursor_details authid current_user as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value.tpb b/source/expectations/data_values/ut_data_value.tpb index 6b1763dd0..05eb3022e 100644 --- a/source/expectations/data_values/ut_data_value.tpb +++ b/source/expectations/data_values/ut_data_value.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value as +create or replace noneditionable type body ut_data_value as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value.tps b/source/expectations/data_values/ut_data_value.tps index beda3d911..6e362a56d 100644 --- a/source/expectations/data_values/ut_data_value.tps +++ b/source/expectations/data_values/ut_data_value.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value force authid current_user as object ( +create or replace noneditionable type ut_data_value force authid current_user as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_anydata.tpb b/source/expectations/data_values/ut_data_value_anydata.tpb index f6fbdc840..9fccb7582 100644 --- a/source/expectations/data_values/ut_data_value_anydata.tpb +++ b/source/expectations/data_values/ut_data_value_anydata.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_anydata as +create or replace noneditionable type body ut_data_value_anydata as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_anydata.tps b/source/expectations/data_values/ut_data_value_anydata.tps index 60dba5515..6ad692d31 100644 --- a/source/expectations/data_values/ut_data_value_anydata.tps +++ b/source/expectations/data_values/ut_data_value_anydata.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_anydata under ut_data_value_refcursor( +create or replace noneditionable type ut_data_value_anydata under ut_data_value_refcursor( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_blob.tpb b/source/expectations/data_values/ut_data_value_blob.tpb index e145a8d4f..c961ebaab 100644 --- a/source/expectations/data_values/ut_data_value_blob.tpb +++ b/source/expectations/data_values/ut_data_value_blob.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_blob as +create or replace noneditionable type body ut_data_value_blob as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_blob.tps b/source/expectations/data_values/ut_data_value_blob.tps index 6df3f3273..94124bfec 100644 --- a/source/expectations/data_values/ut_data_value_blob.tps +++ b/source/expectations/data_values/ut_data_value_blob.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_blob under ut_data_value( +create or replace noneditionable type ut_data_value_blob under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_boolean.tpb b/source/expectations/data_values/ut_data_value_boolean.tpb index 867c3c19c..dd253442c 100644 --- a/source/expectations/data_values/ut_data_value_boolean.tpb +++ b/source/expectations/data_values/ut_data_value_boolean.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_boolean as +create or replace noneditionable type body ut_data_value_boolean as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_boolean.tps b/source/expectations/data_values/ut_data_value_boolean.tps index e25b6e0fd..b6df4e0b4 100644 --- a/source/expectations/data_values/ut_data_value_boolean.tps +++ b/source/expectations/data_values/ut_data_value_boolean.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_boolean under ut_data_value( +create or replace noneditionable type ut_data_value_boolean under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_clob.tpb b/source/expectations/data_values/ut_data_value_clob.tpb index f8a53a035..76a55545e 100644 --- a/source/expectations/data_values/ut_data_value_clob.tpb +++ b/source/expectations/data_values/ut_data_value_clob.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_clob as +create or replace noneditionable type body ut_data_value_clob as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_clob.tps b/source/expectations/data_values/ut_data_value_clob.tps index 6869798d1..686757e85 100644 --- a/source/expectations/data_values/ut_data_value_clob.tps +++ b/source/expectations/data_values/ut_data_value_clob.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_clob under ut_data_value( +create or replace noneditionable type ut_data_value_clob under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_date.tpb b/source/expectations/data_values/ut_data_value_date.tpb index 20424d193..2a1232a1c 100644 --- a/source/expectations/data_values/ut_data_value_date.tpb +++ b/source/expectations/data_values/ut_data_value_date.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_date as +create or replace noneditionable type body ut_data_value_date as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_date.tps b/source/expectations/data_values/ut_data_value_date.tps index f64a577e0..0bb9a17cf 100644 --- a/source/expectations/data_values/ut_data_value_date.tps +++ b/source/expectations/data_values/ut_data_value_date.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_date under ut_data_value( +create or replace noneditionable type ut_data_value_date under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_dsinterval.tpb b/source/expectations/data_values/ut_data_value_dsinterval.tpb index 026d4970f..adad0ef6d 100644 --- a/source/expectations/data_values/ut_data_value_dsinterval.tpb +++ b/source/expectations/data_values/ut_data_value_dsinterval.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_dsinterval as +create or replace noneditionable type body ut_data_value_dsinterval as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_dsinterval.tps b/source/expectations/data_values/ut_data_value_dsinterval.tps index 6f5cb708c..3d15e2e09 100644 --- a/source/expectations/data_values/ut_data_value_dsinterval.tps +++ b/source/expectations/data_values/ut_data_value_dsinterval.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_dsinterval under ut_data_value( +create or replace noneditionable type ut_data_value_dsinterval under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_json.tpb b/source/expectations/data_values/ut_data_value_json.tpb index b703af181..e1b5aa995 100644 --- a/source/expectations/data_values/ut_data_value_json.tpb +++ b/source/expectations/data_values/ut_data_value_json.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_json as +create or replace noneditionable type body ut_data_value_json as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_json.tps b/source/expectations/data_values/ut_data_value_json.tps index 3b77e54eb..c89088564 100644 --- a/source/expectations/data_values/ut_data_value_json.tps +++ b/source/expectations/data_values/ut_data_value_json.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_json under ut_compound_data_value( +create or replace noneditionable type ut_data_value_json under ut_compound_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_number.tpb b/source/expectations/data_values/ut_data_value_number.tpb index 382e6e9aa..3ea08bbd4 100644 --- a/source/expectations/data_values/ut_data_value_number.tpb +++ b/source/expectations/data_values/ut_data_value_number.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_number as +create or replace noneditionable type body ut_data_value_number as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_number.tps b/source/expectations/data_values/ut_data_value_number.tps index 86d68f356..e7031de72 100644 --- a/source/expectations/data_values/ut_data_value_number.tps +++ b/source/expectations/data_values/ut_data_value_number.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_number under ut_data_value( +create or replace noneditionable type ut_data_value_number under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_refcursor.tpb b/source/expectations/data_values/ut_data_value_refcursor.tpb index a4f0cf7a0..b7f79ac52 100644 --- a/source/expectations/data_values/ut_data_value_refcursor.tpb +++ b/source/expectations/data_values/ut_data_value_refcursor.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_refcursor as +create or replace noneditionable type body ut_data_value_refcursor as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_refcursor.tps b/source/expectations/data_values/ut_data_value_refcursor.tps index 594695e32..90a305d32 100644 --- a/source/expectations/data_values/ut_data_value_refcursor.tps +++ b/source/expectations/data_values/ut_data_value_refcursor.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_refcursor under ut_compound_data_value( +create or replace noneditionable type ut_data_value_refcursor under ut_compound_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_timestamp.tpb b/source/expectations/data_values/ut_data_value_timestamp.tpb index 318f799a3..c6aabf7a3 100644 --- a/source/expectations/data_values/ut_data_value_timestamp.tpb +++ b/source/expectations/data_values/ut_data_value_timestamp.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_timestamp as +create or replace noneditionable type body ut_data_value_timestamp as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_timestamp.tps b/source/expectations/data_values/ut_data_value_timestamp.tps index c047e3c79..938b0d63c 100644 --- a/source/expectations/data_values/ut_data_value_timestamp.tps +++ b/source/expectations/data_values/ut_data_value_timestamp.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_timestamp under ut_data_value( +create or replace noneditionable type ut_data_value_timestamp under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_timestamp_ltz.tpb b/source/expectations/data_values/ut_data_value_timestamp_ltz.tpb index 6127de5f1..025314f4b 100644 --- a/source/expectations/data_values/ut_data_value_timestamp_ltz.tpb +++ b/source/expectations/data_values/ut_data_value_timestamp_ltz.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_timestamp_ltz as +create or replace noneditionable type body ut_data_value_timestamp_ltz as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_timestamp_ltz.tps b/source/expectations/data_values/ut_data_value_timestamp_ltz.tps index 7bb296bc9..e7c61a878 100644 --- a/source/expectations/data_values/ut_data_value_timestamp_ltz.tps +++ b/source/expectations/data_values/ut_data_value_timestamp_ltz.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_timestamp_ltz under ut_data_value( +create or replace noneditionable type ut_data_value_timestamp_ltz under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_timestamp_tz.tpb b/source/expectations/data_values/ut_data_value_timestamp_tz.tpb index d5d5cdd13..6af5fea4d 100644 --- a/source/expectations/data_values/ut_data_value_timestamp_tz.tpb +++ b/source/expectations/data_values/ut_data_value_timestamp_tz.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_timestamp_tz as +create or replace noneditionable type body ut_data_value_timestamp_tz as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_timestamp_tz.tps b/source/expectations/data_values/ut_data_value_timestamp_tz.tps index 01cf11124..ad190d197 100644 --- a/source/expectations/data_values/ut_data_value_timestamp_tz.tps +++ b/source/expectations/data_values/ut_data_value_timestamp_tz.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_timestamp_tz under ut_data_value( +create or replace noneditionable type ut_data_value_timestamp_tz under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_varchar2.tpb b/source/expectations/data_values/ut_data_value_varchar2.tpb index d04398697..7a26d7571 100644 --- a/source/expectations/data_values/ut_data_value_varchar2.tpb +++ b/source/expectations/data_values/ut_data_value_varchar2.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_varchar2 as +create or replace noneditionable type body ut_data_value_varchar2 as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_varchar2.tps b/source/expectations/data_values/ut_data_value_varchar2.tps index 3fbeb378b..56effd460 100644 --- a/source/expectations/data_values/ut_data_value_varchar2.tps +++ b/source/expectations/data_values/ut_data_value_varchar2.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_varchar2 under ut_data_value( +create or replace noneditionable type ut_data_value_varchar2 under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_xmltype.tpb b/source/expectations/data_values/ut_data_value_xmltype.tpb index 4b21a8937..f5ffcbc9a 100644 --- a/source/expectations/data_values/ut_data_value_xmltype.tpb +++ b/source/expectations/data_values/ut_data_value_xmltype.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_xmltype as +create or replace noneditionable type body ut_data_value_xmltype as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_xmltype.tps b/source/expectations/data_values/ut_data_value_xmltype.tps index 9ba738b88..2d0257918 100644 --- a/source/expectations/data_values/ut_data_value_xmltype.tps +++ b/source/expectations/data_values/ut_data_value_xmltype.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_xmltype under ut_data_value( +create or replace noneditionable type ut_data_value_xmltype under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_yminterval.tpb b/source/expectations/data_values/ut_data_value_yminterval.tpb index a3a1e7a2f..8d12d3544 100644 --- a/source/expectations/data_values/ut_data_value_yminterval.tpb +++ b/source/expectations/data_values/ut_data_value_yminterval.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_data_value_yminterval as +create or replace noneditionable type body ut_data_value_yminterval as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_data_value_yminterval.tps b/source/expectations/data_values/ut_data_value_yminterval.tps index c9ad2e776..8c3ad8079 100644 --- a/source/expectations/data_values/ut_data_value_yminterval.tps +++ b/source/expectations/data_values/ut_data_value_yminterval.tps @@ -1,7 +1,7 @@ -create or replace type ut_data_value_yminterval under ut_data_value( +create or replace noneditionable type ut_data_value_yminterval under ut_data_value( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_json_data_diff_tmp.sql b/source/expectations/data_values/ut_json_data_diff_tmp.sql index 5ff3440d8..4eda07d7c 100644 --- a/source/expectations/data_values/ut_json_data_diff_tmp.sql +++ b/source/expectations/data_values/ut_json_data_diff_tmp.sql @@ -1,7 +1,7 @@ create global temporary table ut_json_data_diff_tmp( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 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 diff --git a/source/expectations/data_values/ut_json_leaf.tpb b/source/expectations/data_values/ut_json_leaf.tpb index 83f1009ef..d6c862286 100644 --- a/source/expectations/data_values/ut_json_leaf.tpb +++ b/source/expectations/data_values/ut_json_leaf.tpb @@ -1,5 +1,21 @@ -create or replace type body ut_json_leaf as - +create or replace noneditionable type body ut_json_leaf as + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. + */ + member procedure init( self in out nocopy ut_json_leaf, a_element_name varchar2, a_element_value varchar2,a_parent_name varchar2, a_access_path varchar2, a_hierarchy_level integer, a_index_position integer, a_json_type in varchar2, diff --git a/source/expectations/data_values/ut_json_leaf.tps b/source/expectations/data_values/ut_json_leaf.tps index 8aa8afd6a..ac478e34e 100644 --- a/source/expectations/data_values/ut_json_leaf.tps +++ b/source/expectations/data_values/ut_json_leaf.tps @@ -1,7 +1,7 @@ -create or replace type ut_json_leaf authid current_user as object ( +create or replace noneditionable type ut_json_leaf authid current_user as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_json_leaf_tab.tps b/source/expectations/data_values/ut_json_leaf_tab.tps index 395ab5d9e..c00f2a3a1 100644 --- a/source/expectations/data_values/ut_json_leaf_tab.tps +++ b/source/expectations/data_values/ut_json_leaf_tab.tps @@ -1,7 +1,7 @@ -create or replace type ut_json_leaf_tab as +create or replace noneditionable type ut_json_leaf_tab as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_json_tree_details.tpb b/source/expectations/data_values/ut_json_tree_details.tpb index a333ea71c..0d457b2c5 100644 --- a/source/expectations/data_values/ut_json_tree_details.tpb +++ b/source/expectations/data_values/ut_json_tree_details.tpb @@ -1,4 +1,20 @@ -create or replace type body ut_json_tree_details as +create or replace noneditionable type body ut_json_tree_details as + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. + */ member function get_json_type(a_json_piece json_element_t) return varchar2 is begin diff --git a/source/expectations/data_values/ut_json_tree_details.tps b/source/expectations/data_values/ut_json_tree_details.tps index 20ac2fdcc..4f7e891be 100644 --- a/source/expectations/data_values/ut_json_tree_details.tps +++ b/source/expectations/data_values/ut_json_tree_details.tps @@ -1,7 +1,7 @@ -create or replace type ut_json_tree_details force as object ( +create or replace noneditionable type ut_json_tree_details force as object ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_key_anyval_pair.tps b/source/expectations/data_values/ut_key_anyval_pair.tps index d1068f109..6ed63ae7e 100644 --- a/source/expectations/data_values/ut_key_anyval_pair.tps +++ b/source/expectations/data_values/ut_key_anyval_pair.tps @@ -1,7 +1,7 @@ -create or replace type ut_key_anyval_pair force as object( +create or replace noneditionable type ut_key_anyval_pair force as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_key_anyval_pairs.tps b/source/expectations/data_values/ut_key_anyval_pairs.tps index 7e10bf50f..a75305f79 100644 --- a/source/expectations/data_values/ut_key_anyval_pairs.tps +++ b/source/expectations/data_values/ut_key_anyval_pairs.tps @@ -1,7 +1,7 @@ -create or replace type ut_key_anyval_pairs as +create or replace noneditionable type ut_key_anyval_pairs as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_key_anyvalues.tpb b/source/expectations/data_values/ut_key_anyvalues.tpb index d30d8a841..4e6dd2c0f 100644 --- a/source/expectations/data_values/ut_key_anyvalues.tpb +++ b/source/expectations/data_values/ut_key_anyvalues.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_key_anyvalues as +create or replace noneditionable type body ut_key_anyvalues as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/data_values/ut_key_anyvalues.tps b/source/expectations/data_values/ut_key_anyvalues.tps index 8c672bd00..21a9a5c72 100644 --- a/source/expectations/data_values/ut_key_anyvalues.tps +++ b/source/expectations/data_values/ut_key_anyvalues.tps @@ -1,7 +1,7 @@ -create or replace type ut_key_anyvalues under ut_event_item ( +create or replace noneditionable type ut_key_anyvalues under ut_event_item ( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/json_objects_specs.sql b/source/expectations/json_objects_specs.sql index a5d936afb..c0fc15ed6 100644 --- a/source/expectations/json_objects_specs.sql +++ b/source/expectations/json_objects_specs.sql @@ -2,13 +2,13 @@ BEGIN null; $if dbms_db_version.version < 21 $then dbms_output.put_line('Installing json structures specs for native json.'); - execute immediate q'[create or replace TYPE JSON FORCE AUTHID CURRENT_USER AS OBJECT( + execute immediate q'[create or replace noneditionable TYPE JSON FORCE AUTHID CURRENT_USER AS OBJECT( dummyobjt NUMBER ) NOT FINAL NOT INSTANTIABLE;]'; $end $if dbms_db_version.version = 12 and dbms_db_version.release = 1 or dbms_db_version.version < 12 $then dbms_output.put_line('Installing json structures specs.'); - execute immediate q'[create or replace TYPE JSON_Element_T FORCE AUTHID CURRENT_USER AS OBJECT( + execute immediate q'[create or replace noneditionable TYPE JSON_Element_T FORCE AUTHID CURRENT_USER AS OBJECT( dummyobjt NUMBER, STATIC FUNCTION parse(jsn VARCHAR2) RETURN JSON_Element_T, STATIC FUNCTION parse(jsn CLOB) RETURN JSON_Element_T, @@ -34,9 +34,9 @@ BEGIN MEMBER FUNCTION get_Size(self IN JSON_ELEMENT_T) RETURN NUMBER ) NOT FINAL NOT INSTANTIABLE;]'; - execute immediate q'[create or replace TYPE JSON_KEY_LIST FORCE AS VARRAY(32767) OF VARCHAR2(4000);]'; + execute immediate q'[create or replace noneditionable TYPE JSON_KEY_LIST FORCE AS VARRAY(32767) OF VARCHAR2(4000);]'; - execute immediate q'[create or replace TYPE JSON_Array_T FORCE AUTHID CURRENT_USER UNDER JSON_Element_T( + execute immediate q'[create or replace noneditionable TYPE JSON_Array_T FORCE AUTHID CURRENT_USER UNDER JSON_Element_T( CONSTRUCTOR FUNCTION JSON_Array_T RETURN SELF AS RESULT, MEMBER FUNCTION get(pos NUMBER) RETURN JSON_Element_T, MEMBER FUNCTION get_String(pos NUMBER) RETURN VARCHAR2, @@ -51,7 +51,7 @@ BEGIN MEMBER FUNCTION get_Type(pos NUMBER) RETURN VARCHAR2 ) FINAL;]'; - execute immediate q'[create or replace TYPE JSON_Object_T AUTHID CURRENT_USER UNDER JSON_Element_T( + execute immediate q'[create or replace noneditionable TYPE JSON_Object_T AUTHID CURRENT_USER UNDER JSON_Element_T( CONSTRUCTOR FUNCTION JSON_Object_T RETURN SELF AS RESULT, MEMBER FUNCTION get(key VARCHAR2) RETURN JSON_Element_T, MEMBER FUNCTION get_Object(key VARCHAR2) RETURN JSON_OBJECT_T, diff --git a/source/expectations/matchers/ut_be_between.tpb b/source/expectations/matchers/ut_be_between.tpb index 6aea96f55..83dfc4c25 100644 --- a/source/expectations/matchers/ut_be_between.tpb +++ b/source/expectations/matchers/ut_be_between.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_between is +create or replace noneditionable type body ut_be_between is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_between.tps b/source/expectations/matchers/ut_be_between.tps index 9e984085b..7f1403847 100644 --- a/source/expectations/matchers/ut_be_between.tps +++ b/source/expectations/matchers/ut_be_between.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_between under ut_matcher( +create or replace noneditionable type ut_be_between under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_empty.tpb b/source/expectations/matchers/ut_be_empty.tpb index e5e02f6b2..af233aa8f 100644 --- a/source/expectations/matchers/ut_be_empty.tpb +++ b/source/expectations/matchers/ut_be_empty.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_empty as +create or replace noneditionable type body ut_be_empty as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_empty.tps b/source/expectations/matchers/ut_be_empty.tps index 9e9f899d0..a5672ba78 100644 --- a/source/expectations/matchers/ut_be_empty.tps +++ b/source/expectations/matchers/ut_be_empty.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_empty under ut_matcher( +create or replace noneditionable type ut_be_empty under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_false.tpb b/source/expectations/matchers/ut_be_false.tpb index 4cc88600a..a02ac58cb 100644 --- a/source/expectations/matchers/ut_be_false.tpb +++ b/source/expectations/matchers/ut_be_false.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_false as +create or replace noneditionable type body ut_be_false as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_false.tps b/source/expectations/matchers/ut_be_false.tps index ede510719..d793844c1 100644 --- a/source/expectations/matchers/ut_be_false.tps +++ b/source/expectations/matchers/ut_be_false.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_false under ut_matcher( +create or replace noneditionable type ut_be_false under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_greater_or_equal.tpb b/source/expectations/matchers/ut_be_greater_or_equal.tpb index 1c2a19c09..6e564a90c 100644 --- a/source/expectations/matchers/ut_be_greater_or_equal.tpb +++ b/source/expectations/matchers/ut_be_greater_or_equal.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_greater_or_equal AS +create or replace noneditionable type body ut_be_greater_or_equal AS /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_greater_or_equal.tps b/source/expectations/matchers/ut_be_greater_or_equal.tps index b3ab54ff1..d3e651958 100644 --- a/source/expectations/matchers/ut_be_greater_or_equal.tps +++ b/source/expectations/matchers/ut_be_greater_or_equal.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_greater_or_equal under ut_comparison_matcher( +create or replace noneditionable type ut_be_greater_or_equal under ut_comparison_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_greater_than.tpb b/source/expectations/matchers/ut_be_greater_than.tpb index 9cce79119..08628ae3a 100644 --- a/source/expectations/matchers/ut_be_greater_than.tpb +++ b/source/expectations/matchers/ut_be_greater_than.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_greater_than AS +create or replace noneditionable type body ut_be_greater_than AS /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_greater_than.tps b/source/expectations/matchers/ut_be_greater_than.tps index a5a44692d..745b19f08 100644 --- a/source/expectations/matchers/ut_be_greater_than.tps +++ b/source/expectations/matchers/ut_be_greater_than.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_greater_than under ut_comparison_matcher( +create or replace noneditionable type ut_be_greater_than under ut_comparison_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_less_or_equal.tpb b/source/expectations/matchers/ut_be_less_or_equal.tpb index 6acec1dda..2df06a078 100644 --- a/source/expectations/matchers/ut_be_less_or_equal.tpb +++ b/source/expectations/matchers/ut_be_less_or_equal.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_less_or_equal AS +create or replace noneditionable type body ut_be_less_or_equal AS /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_less_or_equal.tps b/source/expectations/matchers/ut_be_less_or_equal.tps index 118611a41..f0e297ad1 100644 --- a/source/expectations/matchers/ut_be_less_or_equal.tps +++ b/source/expectations/matchers/ut_be_less_or_equal.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_less_or_equal under ut_comparison_matcher( +create or replace noneditionable type ut_be_less_or_equal under ut_comparison_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_less_than.tpb b/source/expectations/matchers/ut_be_less_than.tpb index 7608e4917..c0b059231 100644 --- a/source/expectations/matchers/ut_be_less_than.tpb +++ b/source/expectations/matchers/ut_be_less_than.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_less_than as +create or replace noneditionable type body ut_be_less_than as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_less_than.tps b/source/expectations/matchers/ut_be_less_than.tps index b652de789..594df9b71 100644 --- a/source/expectations/matchers/ut_be_less_than.tps +++ b/source/expectations/matchers/ut_be_less_than.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_less_than under ut_comparison_matcher( +create or replace noneditionable type ut_be_less_than under ut_comparison_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_like.tpb b/source/expectations/matchers/ut_be_like.tpb index 6865c2942..b94c3fa91 100644 --- a/source/expectations/matchers/ut_be_like.tpb +++ b/source/expectations/matchers/ut_be_like.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_like as +create or replace noneditionable type body ut_be_like as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_like.tps b/source/expectations/matchers/ut_be_like.tps index dae93a1d9..ca9ad45de 100644 --- a/source/expectations/matchers/ut_be_like.tps +++ b/source/expectations/matchers/ut_be_like.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_like under ut_matcher( +create or replace noneditionable type ut_be_like under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_not_null.tpb b/source/expectations/matchers/ut_be_not_null.tpb index 194e2ea32..d291d2e35 100644 --- a/source/expectations/matchers/ut_be_not_null.tpb +++ b/source/expectations/matchers/ut_be_not_null.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_not_null as +create or replace noneditionable type body ut_be_not_null as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_not_null.tps b/source/expectations/matchers/ut_be_not_null.tps index 196aac9fb..61f6c871d 100644 --- a/source/expectations/matchers/ut_be_not_null.tps +++ b/source/expectations/matchers/ut_be_not_null.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_not_null under ut_matcher( +create or replace noneditionable type ut_be_not_null under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_null.tpb b/source/expectations/matchers/ut_be_null.tpb index 8c672791b..89fbaf563 100644 --- a/source/expectations/matchers/ut_be_null.tpb +++ b/source/expectations/matchers/ut_be_null.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_null as +create or replace noneditionable type body ut_be_null as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_null.tps b/source/expectations/matchers/ut_be_null.tps index 3b2d6ac84..063c7db29 100644 --- a/source/expectations/matchers/ut_be_null.tps +++ b/source/expectations/matchers/ut_be_null.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_null under ut_matcher( +create or replace noneditionable type ut_be_null under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_true.tpb b/source/expectations/matchers/ut_be_true.tpb index fe78b4e22..59368f342 100644 --- a/source/expectations/matchers/ut_be_true.tpb +++ b/source/expectations/matchers/ut_be_true.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_true as +create or replace noneditionable type body ut_be_true as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_true.tps b/source/expectations/matchers/ut_be_true.tps index 8b5491e39..19d397371 100644 --- a/source/expectations/matchers/ut_be_true.tps +++ b/source/expectations/matchers/ut_be_true.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_true under ut_matcher( +create or replace noneditionable type ut_be_true under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_within.tpb b/source/expectations/matchers/ut_be_within.tpb index e2aa792d0..f8f880ace 100644 --- a/source/expectations/matchers/ut_be_within.tpb +++ b/source/expectations/matchers/ut_be_within.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_within as +create or replace noneditionable type body ut_be_within as /* utPLSQL - Version 3 - Copyright 2016 - 2019 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_within.tps b/source/expectations/matchers/ut_be_within.tps index 576f9ff37..276c529df 100644 --- a/source/expectations/matchers/ut_be_within.tps +++ b/source/expectations/matchers/ut_be_within.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_within force under ut_be_within_pct( +create or replace noneditionable type ut_be_within force under ut_be_within_pct( /* utPLSQL - Version 3 - Copyright 2016 - 2019 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_within_helper.pkb b/source/expectations/matchers/ut_be_within_helper.pkb index 5dd82baa2..635c1b111 100644 --- a/source/expectations/matchers/ut_be_within_helper.pkb +++ b/source/expectations/matchers/ut_be_within_helper.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_be_within_helper as +create or replace noneditionable package body ut_be_within_helper as /* utPLSQL - Version 3 - Copyright 2016 - 2019 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_within_helper.pks b/source/expectations/matchers/ut_be_within_helper.pks index 41737cc8f..5a59eea9e 100644 --- a/source/expectations/matchers/ut_be_within_helper.pks +++ b/source/expectations/matchers/ut_be_within_helper.pks @@ -1,7 +1,7 @@ -create or replace package ut_be_within_helper authid definer as +create or replace noneditionable package ut_be_within_helper authid definer as /* utPLSQL - Version 3 - Copyright 2016 - 2019 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_within_pct.tpb b/source/expectations/matchers/ut_be_within_pct.tpb index 8b280ffff..82ce8e84e 100644 --- a/source/expectations/matchers/ut_be_within_pct.tpb +++ b/source/expectations/matchers/ut_be_within_pct.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_be_within_pct as +create or replace noneditionable type body ut_be_within_pct as /* utPLSQL - Version 3 - Copyright 2016 - 2019 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_be_within_pct.tps b/source/expectations/matchers/ut_be_within_pct.tps index 499e9a2d8..810eaa5b3 100644 --- a/source/expectations/matchers/ut_be_within_pct.tps +++ b/source/expectations/matchers/ut_be_within_pct.tps @@ -1,7 +1,7 @@ -create or replace type ut_be_within_pct force under ut_comparison_matcher( +create or replace noneditionable type ut_be_within_pct force under ut_comparison_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2019 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ create or replace type ut_be_within_pct force under ut_comparison_matcher( limitations under the License. */ - /** * Holds information about mather options */ diff --git a/source/expectations/matchers/ut_comparison_matcher.tpb b/source/expectations/matchers/ut_comparison_matcher.tpb index 20ed35927..d6c47855d 100644 --- a/source/expectations/matchers/ut_comparison_matcher.tpb +++ b/source/expectations/matchers/ut_comparison_matcher.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_comparison_matcher as +create or replace noneditionable type body ut_comparison_matcher as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_comparison_matcher.tps b/source/expectations/matchers/ut_comparison_matcher.tps index 68ecb4462..b1ed08a84 100644 --- a/source/expectations/matchers/ut_comparison_matcher.tps +++ b/source/expectations/matchers/ut_comparison_matcher.tps @@ -1,7 +1,7 @@ -create or replace type ut_comparison_matcher under ut_matcher( +create or replace noneditionable type ut_comparison_matcher under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_contain.tpb b/source/expectations/matchers/ut_contain.tpb index 7591925f5..bcc3806b7 100644 --- a/source/expectations/matchers/ut_contain.tpb +++ b/source/expectations/matchers/ut_contain.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_contain as +create or replace noneditionable type body ut_contain as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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/matchers/ut_contain.tps b/source/expectations/matchers/ut_contain.tps index e572edf62..bd67113c9 100644 --- a/source/expectations/matchers/ut_contain.tps +++ b/source/expectations/matchers/ut_contain.tps @@ -1,7 +1,7 @@ -create or replace type ut_contain under ut_equal( +create or replace noneditionable type ut_contain under ut_equal( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_equal.tpb b/source/expectations/matchers/ut_equal.tpb index d514e34d0..f999995d1 100644 --- a/source/expectations/matchers/ut_equal.tpb +++ b/source/expectations/matchers/ut_equal.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_equal as +create or replace noneditionable type body ut_equal as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_equal.tps b/source/expectations/matchers/ut_equal.tps index e48b797ed..eeb715a58 100644 --- a/source/expectations/matchers/ut_equal.tps +++ b/source/expectations/matchers/ut_equal.tps @@ -1,7 +1,7 @@ -create or replace type ut_equal force under ut_comparison_matcher( +create or replace noneditionable type ut_equal force under ut_comparison_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_have_count.tpb b/source/expectations/matchers/ut_have_count.tpb index 457cd6272..f6d3d2682 100644 --- a/source/expectations/matchers/ut_have_count.tpb +++ b/source/expectations/matchers/ut_have_count.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_have_count as +create or replace noneditionable type body ut_have_count as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_have_count.tps b/source/expectations/matchers/ut_have_count.tps index d48dc44c8..85cdf4393 100644 --- a/source/expectations/matchers/ut_have_count.tps +++ b/source/expectations/matchers/ut_have_count.tps @@ -1,7 +1,7 @@ -create or replace type ut_have_count under ut_matcher( +create or replace noneditionable type ut_have_count under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_match.tpb b/source/expectations/matchers/ut_match.tpb index 3c7a71402..1836298e7 100644 --- a/source/expectations/matchers/ut_match.tpb +++ b/source/expectations/matchers/ut_match.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_match as +create or replace noneditionable type body ut_match as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_match.tps b/source/expectations/matchers/ut_match.tps index ee51a4c28..3f840545f 100644 --- a/source/expectations/matchers/ut_match.tps +++ b/source/expectations/matchers/ut_match.tps @@ -1,7 +1,7 @@ -create or replace type ut_match under ut_matcher( +create or replace noneditionable type ut_match under ut_matcher( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_matcher.tpb b/source/expectations/matchers/ut_matcher.tpb index 6b5b98465..828e1ac51 100644 --- a/source/expectations/matchers/ut_matcher.tpb +++ b/source/expectations/matchers/ut_matcher.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_matcher as +create or replace noneditionable type body ut_matcher as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_matcher.tps b/source/expectations/matchers/ut_matcher.tps index a034e4ed2..ee7f1b5dd 100644 --- a/source/expectations/matchers/ut_matcher.tps +++ b/source/expectations/matchers/ut_matcher.tps @@ -1,7 +1,7 @@ -create or replace type ut_matcher under ut_matcher_base( +create or replace noneditionable type ut_matcher under ut_matcher_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_matcher_base.tps b/source/expectations/matchers/ut_matcher_base.tps index ef7fd98a2..ba79a6aed 100644 --- a/source/expectations/matchers/ut_matcher_base.tps +++ b/source/expectations/matchers/ut_matcher_base.tps @@ -1,4 +1,21 @@ -create or replace type ut_matcher_base force authid current_user as object( +create or replace noneditionable type ut_matcher_base force authid current_user as object( + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. + */ + self_type varchar2(250) ) not final not instantiable diff --git a/source/expectations/matchers/ut_matcher_options.tpb b/source/expectations/matchers/ut_matcher_options.tpb index 8730c204b..57aca2617 100644 --- a/source/expectations/matchers/ut_matcher_options.tpb +++ b/source/expectations/matchers/ut_matcher_options.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_matcher_options as +create or replace noneditionable type body ut_matcher_options as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_matcher_options.tps b/source/expectations/matchers/ut_matcher_options.tps index 276cad2ec..db076fd99 100644 --- a/source/expectations/matchers/ut_matcher_options.tps +++ b/source/expectations/matchers/ut_matcher_options.tps @@ -1,7 +1,7 @@ -create or replace type ut_matcher_options authid current_user as object( +create or replace noneditionable type ut_matcher_options authid current_user as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_matcher_options_items.tpb b/source/expectations/matchers/ut_matcher_options_items.tpb index 92da588ce..6e4841870 100644 --- a/source/expectations/matchers/ut_matcher_options_items.tpb +++ b/source/expectations/matchers/ut_matcher_options_items.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_matcher_options_items is +create or replace noneditionable type body ut_matcher_options_items is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/matchers/ut_matcher_options_items.tps b/source/expectations/matchers/ut_matcher_options_items.tps index 53787070d..045a7ece7 100644 --- a/source/expectations/matchers/ut_matcher_options_items.tps +++ b/source/expectations/matchers/ut_matcher_options_items.tps @@ -1,7 +1,7 @@ -create or replace type ut_matcher_options_items authid current_user as object( +create or replace noneditionable type ut_matcher_options_items authid current_user as object( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/ut_expectation.tpb b/source/expectations/ut_expectation.tpb index a78c57eb7..5f7c9cce0 100644 --- a/source/expectations/ut_expectation.tpb +++ b/source/expectations/ut_expectation.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_expectation as +create or replace noneditionable type body ut_expectation as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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/expectations/ut_expectation.tps b/source/expectations/ut_expectation.tps index 30fe985d3..a423c39a4 100644 --- a/source/expectations/ut_expectation.tps +++ b/source/expectations/ut_expectation.tps @@ -1,7 +1,7 @@ -create or replace type ut_expectation force under ut_expectation_base( +create or replace noneditionable type ut_expectation force under ut_expectation_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/ut_expectation_base.tpb b/source/expectations/ut_expectation_base.tpb index 20b3e7a95..5ed630c63 100644 --- a/source/expectations/ut_expectation_base.tpb +++ b/source/expectations/ut_expectation_base.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_expectation_base as +create or replace noneditionable type body ut_expectation_base as /* utPLSQL - Version 3 - Copyright 2016 - 2019 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ create or replace type body ut_expectation_base as See the License for the specific language governing permissions and limitations under the License. */ + member procedure to_(self in ut_expectation_base, a_matcher ut_matcher_base) is l_expectation_result boolean; l_matcher ut_matcher := treat(a_matcher as ut_matcher); diff --git a/source/expectations/ut_expectation_base.tps b/source/expectations/ut_expectation_base.tps index a542d26ea..86f274495 100644 --- a/source/expectations/ut_expectation_base.tps +++ b/source/expectations/ut_expectation_base.tps @@ -1,7 +1,7 @@ -create or replace type ut_expectation_base authid current_user as object( +create or replace noneditionable type ut_expectation_base authid current_user as object( /* utPLSQL - Version 3 - Copyright 2016 - 2019 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ create or replace type ut_expectation_base authid current_user as object( See the License for the specific language governing permissions and limitations under the License. */ + actual_data ut_data_value, description varchar2(4000 char), diff --git a/source/expectations/ut_expectation_compound.tpb b/source/expectations/ut_expectation_compound.tpb index 6fc2e2647..d76010f80 100644 --- a/source/expectations/ut_expectation_compound.tpb +++ b/source/expectations/ut_expectation_compound.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_expectation_compound as +create or replace noneditionable type body ut_expectation_compound as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/ut_expectation_compound.tps b/source/expectations/ut_expectation_compound.tps index 46da23fea..54de48dd4 100644 --- a/source/expectations/ut_expectation_compound.tps +++ b/source/expectations/ut_expectation_compound.tps @@ -1,7 +1,7 @@ -create or replace type ut_expectation_compound under ut_expectation( +create or replace noneditionable type ut_expectation_compound under ut_expectation( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/ut_expectation_json.tpb b/source/expectations/ut_expectation_json.tpb index b71e8ea83..68d4085ef 100644 --- a/source/expectations/ut_expectation_json.tpb +++ b/source/expectations/ut_expectation_json.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_expectation_json as +create or replace noneditionable type body ut_expectation_json as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/expectations/ut_expectation_json.tps b/source/expectations/ut_expectation_json.tps index 561c5d11f..c75154bc8 100644 --- a/source/expectations/ut_expectation_json.tps +++ b/source/expectations/ut_expectation_json.tps @@ -1,7 +1,7 @@ -create or replace type ut_expectation_json under ut_expectation( +create or replace noneditionable type ut_expectation_json under ut_expectation( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/install.sql b/source/install.sql index 6f3c16801..9af0a4f66 100644 --- a/source/install.sql +++ b/source/install.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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' @@ -340,6 +346,8 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'reporters/ut_xunit_reporter.tpb' @@install_component.sql 'reporters/ut_sonar_test_reporter.tps' @@install_component.sql 'reporters/ut_sonar_test_reporter.tpb' +@@install_component.sql 'reporters/ut_tap_reporter.tps' +@@install_component.sql 'reporters/ut_tap_reporter.tpb' @@install_component.sql 'reporters/ut_coverage_html_reporter.tps' @@install_component.sql 'reporters/ut_coverage_report_html_helper.pks' @@ -347,8 +355,6 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'reporters/ut_coverage_html_reporter.tpb' @@install_component.sql 'reporters/ut_coverage_sonar_reporter.tps' @@install_component.sql 'reporters/ut_coverage_sonar_reporter.tpb' -@@install_component.sql 'reporters/ut_coveralls_reporter.tps' -@@install_component.sql 'reporters/ut_coveralls_reporter.tpb' @@install_component.sql 'reporters/ut_coverage_cobertura_reporter.tps' @@install_component.sql 'reporters/ut_coverage_cobertura_reporter.tpb' @@install_component.sql 'reporters/ut_realtime_reporter.tps' diff --git a/source/install_above_12_1.sql b/source/install_above_12_1.sql index 69e47ce9a..5a1943358 100644 --- a/source/install_above_12_1.sql +++ b/source/install_above_12_1.sql @@ -1,3 +1,20 @@ +/* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. +*/ + def FILE_NAME = '&&1' column SCRIPT_NAME new_value SCRIPT_NAME noprint diff --git a/source/install_component.sql b/source/install_component.sql index 373c85c54..1189df496 100644 --- a/source/install_component.sql +++ b/source/install_component.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/install_ddl_trigger.sql b/source/install_ddl_trigger.sql index bc147c2be..b23b4bc63 100644 --- a/source/install_ddl_trigger.sql +++ b/source/install_ddl_trigger.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/install_headless.sql b/source/install_headless.sql index 183262675..be1e5becf 100644 --- a/source/install_headless.sql +++ b/source/install_headless.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/install_headless_with_trigger.sql b/source/install_headless_with_trigger.sql index 76ae48ccb..2c05403c5 100644 --- a/source/install_headless_with_trigger.sql +++ b/source/install_headless_with_trigger.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_ansiconsole_helper.pkb b/source/reporters/ut_ansiconsole_helper.pkb index 53e6bc6aa..e8ee34531 100644 --- a/source/reporters/ut_ansiconsole_helper.pkb +++ b/source/reporters/ut_ansiconsole_helper.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_ansiconsole_helper as +create or replace noneditionable package body ut_ansiconsole_helper as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_ansiconsole_helper.pks b/source/reporters/ut_ansiconsole_helper.pks index 217d36a49..e882de293 100644 --- a/source/reporters/ut_ansiconsole_helper.pks +++ b/source/reporters/ut_ansiconsole_helper.pks @@ -1,7 +1,7 @@ -create or replace package ut_ansiconsole_helper as +create or replace noneditionable package ut_ansiconsole_helper as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_coverage_cobertura_reporter.tpb b/source/reporters/ut_coverage_cobertura_reporter.tpb index 48082a6d4..45c1935b1 100644 --- a/source/reporters/ut_coverage_cobertura_reporter.tpb +++ b/source/reporters/ut_coverage_cobertura_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_coverage_cobertura_reporter is +create or replace noneditionable type body ut_coverage_cobertura_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_cobertura_reporter.tps b/source/reporters/ut_coverage_cobertura_reporter.tps index 1292e60fb..799840a55 100644 --- a/source/reporters/ut_coverage_cobertura_reporter.tps +++ b/source/reporters/ut_coverage_cobertura_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_coverage_cobertura_reporter under ut_coverage_reporter_base( +create or replace noneditionable type ut_coverage_cobertura_reporter under ut_coverage_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_coverage_html_reporter.tpb b/source/reporters/ut_coverage_html_reporter.tpb index c88c91a80..90419371a 100644 --- a/source/reporters/ut_coverage_html_reporter.tpb +++ b/source/reporters/ut_coverage_html_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_coverage_html_reporter is +create or replace noneditionable type body ut_coverage_html_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_html_reporter.tps b/source/reporters/ut_coverage_html_reporter.tps index 42f2fd4e3..23cf45c0b 100644 --- a/source/reporters/ut_coverage_html_reporter.tps +++ b/source/reporters/ut_coverage_html_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_coverage_html_reporter under ut_coverage_reporter_base( +create or replace noneditionable type ut_coverage_html_reporter under ut_coverage_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_coverage_report_html_helper.pkb b/source/reporters/ut_coverage_report_html_helper.pkb index 2bb9a87d9..99c36ab73 100644 --- a/source/reporters/ut_coverage_report_html_helper.pkb +++ b/source/reporters/ut_coverage_report_html_helper.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_coverage_report_html_helper is +create or replace noneditionable package body ut_coverage_report_html_helper is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ create or replace package body ut_coverage_report_html_helper is function object_id(a_object_full_name varchar2) return varchar2 is begin - return rawtohex(dbms_crypto.hash(src => utl_raw.cast_to_raw(a_object_full_name), typ => dbms_crypto.hash_md5)); + return rawtohex(dbms_crypto.hash(src => utl_raw.cast_to_raw(a_object_full_name), typ => dbms_crypto.HASH_SH256)); end; function link_to_source_file(a_object_full_name varchar2) return varchar2 is @@ -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,9 +230,8 @@ 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_id varchar2(100) := object_id(a_title); l_unit_coverage ut_coverage.t_unit_coverage; l_unit ut_coverage.t_object_name; begin diff --git a/source/reporters/ut_coverage_report_html_helper.pks b/source/reporters/ut_coverage_report_html_helper.pks index 42a59c123..8463e3e89 100644 --- a/source/reporters/ut_coverage_report_html_helper.pks +++ b/source/reporters/ut_coverage_report_html_helper.pks @@ -1,7 +1,7 @@ -create or replace package ut_coverage_report_html_helper authid current_user is +create or replace noneditionable package ut_coverage_report_html_helper authid current_user is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_coverage_sonar_reporter.tpb b/source/reporters/ut_coverage_sonar_reporter.tpb index 8ee78dc24..a070572b7 100644 --- a/source/reporters/ut_coverage_sonar_reporter.tpb +++ b/source/reporters/ut_coverage_sonar_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_coverage_sonar_reporter is +create or replace noneditionable type body ut_coverage_sonar_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_coverage_sonar_reporter.tps b/source/reporters/ut_coverage_sonar_reporter.tps index 715bdefd2..fd160bdb5 100644 --- a/source/reporters/ut_coverage_sonar_reporter.tps +++ b/source/reporters/ut_coverage_sonar_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_coverage_sonar_reporter under ut_coverage_reporter_base( +create or replace noneditionable type ut_coverage_sonar_reporter under ut_coverage_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_coveralls_reporter.tpb b/source/reporters/ut_coveralls_reporter.tpb deleted file mode 100644 index 3a54aa7f7..000000000 --- a/source/reporters/ut_coveralls_reporter.tpb +++ /dev/null @@ -1,104 +0,0 @@ -create or replace type body ut_coveralls_reporter is - /* - utPLSQL - Version 3 - Copyright 2016 - 2021 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_coveralls_reporter( - self in out nocopy ut_coveralls_reporter - ) return self as result is - begin - self.init($$plsql_unit); - return; - end; - - overriding member procedure after_calling_run(self in out nocopy ut_coveralls_reporter, a_run in ut_run) as - - function get_lines_json(a_unit_coverage ut_coverage.t_unit_coverage) return ut_varchar2_rows is - l_file_part varchar2(32767); - l_result ut_varchar2_rows := ut_varchar2_rows(); - l_last_line_no binary_integer; - c_coverage_header constant varchar2(30) := '"coverage": ['; - c_null constant varchar2(4) := 'null'; - begin - ut_utils.append_to_list(l_result, c_coverage_header); - - l_last_line_no := a_unit_coverage.lines.last; - if l_last_line_no is null then - ut_utils.append_to_list( - l_result - , rpad( to_clob( '0' ), ( a_unit_coverage.total_lines * 3 ) - 2, ','||chr(10)||'0' ) - ); - else - for line_no in 1 .. l_last_line_no loop - if a_unit_coverage.lines.exists(line_no) then - l_file_part := to_char(a_unit_coverage.lines(line_no).executions); - else - l_file_part := c_null; - end if; - if line_no < l_last_line_no then - l_file_part := l_file_part ||','; - end if; - ut_utils.append_to_list(l_result, l_file_part); - end loop; - end if; - ut_utils.append_to_list(l_result, ']'); - return l_result; - end; - - function get_coverage_json( - a_coverage_data ut_coverage.t_coverage - ) return ut_varchar2_rows is - l_result ut_varchar2_rows := ut_varchar2_rows(); - l_unit ut_coverage.t_object_name; - c_coverage_header constant varchar2(30) := '{"source_files":['; - c_coverage_footer constant varchar2(30) := ']}'||chr(10)||' '; - begin - ut_utils.append_to_list(l_result, c_coverage_header); - l_unit := a_coverage_data.objects.first; - while l_unit is not null loop - ut_utils.append_to_list(l_result, '{ "name": "'||l_unit||'",'); - - ut_utils.append_to_list(l_result,get_lines_json(a_coverage_data.objects(l_unit))); - - ut_utils.append_to_list(l_result, '}'); - - l_unit := a_coverage_data.objects.next(l_unit); - if l_unit is not null then - ut_utils.append_to_list(l_result, ','); - end if; - end loop; - ut_utils.append_to_list(l_result, c_coverage_footer); - return l_result; - end; - begin - ut_coverage.coverage_stop(); - - self.print_text_lines( - get_coverage_json( - ut_coverage.get_coverage_data(a_run.coverage_options) - ) - ); - end; - - overriding member function get_description return varchar2 as - begin - return 'Generates a JSON coverage report providing information on code coverage with line numbers.' || chr(10) || - 'Designed for [Coveralls](https://coveralls.io/).' || chr(10) || - 'JSON format conforms with specification: https://docs.coveralls.io/api-introduction'; - end; - -end; -/ diff --git a/source/reporters/ut_debug_reporter.tpb b/source/reporters/ut_debug_reporter.tpb index 879725172..547437cde 100644 --- a/source/reporters/ut_debug_reporter.tpb +++ b/source/reporters/ut_debug_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_debug_reporter is +create or replace noneditionable type body ut_debug_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_debug_reporter.tps b/source/reporters/ut_debug_reporter.tps index 2123f19cc..edbf1cdc6 100644 --- a/source/reporters/ut_debug_reporter.tps +++ b/source/reporters/ut_debug_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_debug_reporter under ut_output_reporter_base( +create or replace noneditionable type ut_debug_reporter under ut_output_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_documentation_reporter.tpb b/source/reporters/ut_documentation_reporter.tpb index e8c0a4e31..83152ea1c 100644 --- a/source/reporters/ut_documentation_reporter.tpb +++ b/source/reporters/ut_documentation_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_documentation_reporter is +create or replace noneditionable type body ut_documentation_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_documentation_reporter.tps b/source/reporters/ut_documentation_reporter.tps index 422225117..bab9082c6 100644 --- a/source/reporters/ut_documentation_reporter.tps +++ b/source/reporters/ut_documentation_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_documentation_reporter under ut_console_reporter_base( +create or replace noneditionable type ut_documentation_reporter under ut_console_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_junit_reporter.tpb b/source/reporters/ut_junit_reporter.tpb index 44affa030..bf62dc564 100644 --- a/source/reporters/ut_junit_reporter.tpb +++ b/source/reporters/ut_junit_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_junit_reporter is +create or replace noneditionable type body ut_junit_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_junit_reporter.tps b/source/reporters/ut_junit_reporter.tps index 6cdaff1b9..a20913980 100644 --- a/source/reporters/ut_junit_reporter.tps +++ b/source/reporters/ut_junit_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_junit_reporter force under ut_output_reporter_base( +create or replace noneditionable type ut_junit_reporter force under ut_output_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_realtime_reporter.tpb b/source/reporters/ut_realtime_reporter.tpb index 9c2af57ec..a8b7c566e 100644 --- a/source/reporters/ut_realtime_reporter.tpb +++ b/source/reporters/ut_realtime_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_realtime_reporter is +create or replace noneditionable type body ut_realtime_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_realtime_reporter.tps b/source/reporters/ut_realtime_reporter.tps index 0501006e4..b8686685b 100644 --- a/source/reporters/ut_realtime_reporter.tps +++ b/source/reporters/ut_realtime_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_realtime_reporter force under ut_output_reporter_base( +create or replace noneditionable type ut_realtime_reporter force under ut_output_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_sonar_test_reporter.tpb b/source/reporters/ut_sonar_test_reporter.tpb index a3ec72241..da29b629d 100644 --- a/source/reporters/ut_sonar_test_reporter.tpb +++ b/source/reporters/ut_sonar_test_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_sonar_test_reporter is +create or replace noneditionable type body ut_sonar_test_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_sonar_test_reporter.tps b/source/reporters/ut_sonar_test_reporter.tps index ac3b16bd1..0884ad3d3 100644 --- a/source/reporters/ut_sonar_test_reporter.tps +++ b/source/reporters/ut_sonar_test_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_sonar_test_reporter under ut_output_reporter_base( +create or replace noneditionable type ut_sonar_test_reporter under ut_output_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_tap_reporter.tpb b/source/reporters/ut_tap_reporter.tpb new file mode 100644 index 000000000..c807ab3e1 --- /dev/null +++ b/source/reporters/ut_tap_reporter.tpb @@ -0,0 +1,139 @@ +create or replace noneditionable type body ut_tap_reporter is + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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_tap_reporter(self in out nocopy ut_tap_reporter) return self as result is + begin + self.init($$plsql_unit); + self.lvl := 0; + return; + end ut_tap_reporter; + + member procedure print_comment(self in out nocopy ut_tap_reporter, a_comment clob) as + begin + self.print_clob(regexp_replace(a_comment, '^(.+)', '# \1', 1, 0, 'm')); + end print_comment; + + member function escape_special_chars(self in out nocopy ut_tap_reporter, a_string_to_escape clob) return clob as + begin + return regexp_replace(a_string_to_escape, '([\\#])', '\\\1'); + end escape_special_chars; + + overriding member procedure before_calling_suite(self in out nocopy ut_tap_reporter, a_suite ut_logical_suite) as + begin + self.print_text('# Subtest: ' || self.escape_special_chars(coalesce(a_suite.description, a_suite.name))); + lvl := lvl + 2; + self.print_text('1..' || a_suite.items.count); + end before_calling_suite; + + + overriding member procedure after_calling_test(self in out nocopy ut_tap_reporter, a_test ut_test) as + l_message varchar2(4000); + l_test_name varchar2(4000) := self.escape_special_chars(coalesce(a_test.description, a_test.name)); + + procedure print_failed_expectation(a_test ut_test) is + l_lines ut_varchar2_list; + l_failed boolean; + begin + if a_test.get_error_stack_traces().count = 0 then + -- If no error occurred, print failed expectation + l_lines := a_test.all_expectations(a_test.all_expectations.last).get_result_lines(); + l_failed := a_test.all_expectations(a_test.all_expectations.last).status >= ut_utils.gc_success; + if l_failed then + self.print_text('message: ''' || l_lines(1) || ''''); + self.print_text('severity: fail'); + end if; + else + -- Print multi-line YAML-String with implicit newline characters + self.print_text('message: |'); + self.lvl := self.lvl + 1; + self.print_text(ut_utils.table_to_clob(a_test.get_error_stack_traces())); + self.lvl := self.lvl - 1; + self.print_text('severity: error'); + end if; + end print_failed_expectation; + + begin + + if a_test.result = ut_utils.gc_disabled then + self.print_text('ok - ' || l_test_name || ' # SKIP'|| + case when a_test.disabled_reason is not null + then ': '|| self.escape_special_chars(a_test.disabled_reason) + else null + end ); + elsif a_test.result = ut_utils.gc_success then + self.print_text('ok - ' || l_test_name); + elsif a_test.result > ut_utils.gc_success then + if self.lvl = 0 then + self.print_text(ut_ansiconsole_helper.red('not ok') || ' - ' || l_test_name); + else + self.print_text('not ok - ' || l_test_name); + end if; + self.lvl := self.lvl + 1; + self.print_text('---'); + print_failed_expectation(a_test); + self.print_text('...'); + self.lvl := self.lvl - 1; + end if; + + self.print_comment(a_test.get_serveroutputs); + + end after_calling_test; + + overriding member procedure after_calling_before_all(self in out nocopy ut_tap_reporter, a_executable in ut_executable) is + begin + if a_executable.serveroutput is not null and a_executable.serveroutput != empty_clob() then + self.print_comment(a_executable.serveroutput); + end if; + end after_calling_before_all; + + overriding member procedure after_calling_after_all(self in out nocopy ut_tap_reporter, a_executable in ut_executable) is + begin + if a_executable.serveroutput is not null and a_executable.serveroutput != empty_clob() then + self.print_comment(a_executable.serveroutput); + end if; + end after_calling_after_all; + + overriding member procedure after_calling_suite(self in out nocopy ut_tap_reporter, a_suite ut_logical_suite) as + l_suite_name varchar2(4000) := coalesce(a_suite.description, a_suite.name); + begin + lvl := lvl - 2; + if lvl = 0 then + if a_suite.result = ut_utils.gc_success or a_suite.result = ut_utils.gc_disabled then + self.print_text('ok - ' || self.escape_special_chars(l_suite_name)); + elsif a_suite.result > ut_utils.gc_success then + self.print_text(ut_ansiconsole_helper.red('not ok') || ' - ' || self.escape_special_chars(l_suite_name)); + end if; + + self.print_text(' '); + end if; + + end after_calling_suite; + + overriding member procedure before_calling_run(self in out nocopy ut_tap_reporter, a_run in ut_run) as + begin + self.print_text('TAP version 14'); + self.print_text('1..' || a_run.items.count); + self.print_text(' '); + end before_calling_run; + + overriding member procedure after_calling_run(self in out nocopy ut_tap_reporter, a_run in ut_run) as + begin + self.lvl := 0; + end; +end; +/ diff --git a/source/reporters/ut_tap_reporter.tps b/source/reporters/ut_tap_reporter.tps new file mode 100644 index 000000000..150486648 --- /dev/null +++ b/source/reporters/ut_tap_reporter.tps @@ -0,0 +1,34 @@ +create or replace noneditionable type ut_tap_reporter under ut_documentation_reporter( + /* + utPLSQL - Version 3 + Copyright 2016 - 2026 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_tap_reporter(self in out nocopy ut_tap_reporter) return self as result, + member procedure print_comment(self in out nocopy ut_tap_reporter, a_comment clob), + member function escape_special_chars(self in out nocopy ut_tap_reporter, a_string_to_escape clob) return clob, + overriding member procedure before_calling_suite(self in out nocopy ut_tap_reporter, a_suite ut_logical_suite), + + overriding member procedure after_calling_test(self in out nocopy ut_tap_reporter, a_test ut_test), + + overriding member procedure after_calling_before_all (self in out nocopy ut_tap_reporter, a_executable in ut_executable), + overriding member procedure after_calling_after_all (self in out nocopy ut_tap_reporter, a_executable in ut_executable), + overriding member procedure before_calling_run(self in out nocopy ut_tap_reporter, a_run in ut_run), + overriding member procedure after_calling_suite(self in out nocopy ut_tap_reporter, a_suite ut_logical_suite), + overriding member procedure after_calling_run(self in out nocopy ut_tap_reporter, a_run in ut_run) + +) +not final +/ diff --git a/source/reporters/ut_teamcity_reporter.tpb b/source/reporters/ut_teamcity_reporter.tpb index 95bea32b8..94d5497d5 100644 --- a/source/reporters/ut_teamcity_reporter.tpb +++ b/source/reporters/ut_teamcity_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_teamcity_reporter is +create or replace noneditionable type body ut_teamcity_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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.tps b/source/reporters/ut_teamcity_reporter.tps index e03fa3643..2def91c35 100644 --- a/source/reporters/ut_teamcity_reporter.tps +++ b/source/reporters/ut_teamcity_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_teamcity_reporter under ut_output_reporter_base( +create or replace noneditionable type ut_teamcity_reporter under ut_output_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_teamcity_reporter_helper.pkb b/source/reporters/ut_teamcity_reporter_helper.pkb index 84a9d19e8..1d33c43ae 100644 --- a/source/reporters/ut_teamcity_reporter_helper.pkb +++ b/source/reporters/ut_teamcity_reporter_helper.pkb @@ -1,7 +1,7 @@ -create or replace package body ut_teamcity_reporter_helper is +create or replace noneditionable package body ut_teamcity_reporter_helper is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_teamcity_reporter_helper.pks b/source/reporters/ut_teamcity_reporter_helper.pks index d7fa86cdc..bb9acef35 100644 --- a/source/reporters/ut_teamcity_reporter_helper.pks +++ b/source/reporters/ut_teamcity_reporter_helper.pks @@ -1,7 +1,7 @@ -create or replace package ut_teamcity_reporter_helper is +create or replace noneditionable package ut_teamcity_reporter_helper is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_tfs_junit_reporter.tpb b/source/reporters/ut_tfs_junit_reporter.tpb index 710c3bc7f..5e24b2dca 100644 --- a/source/reporters/ut_tfs_junit_reporter.tpb +++ b/source/reporters/ut_tfs_junit_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_tfs_junit_reporter is +create or replace noneditionable type body ut_tfs_junit_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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_tfs_junit_reporter.tps b/source/reporters/ut_tfs_junit_reporter.tps index 61cbc3fd8..560bb10dd 100644 --- a/source/reporters/ut_tfs_junit_reporter.tps +++ b/source/reporters/ut_tfs_junit_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_tfs_junit_reporter under ut_output_reporter_base( +create or replace noneditionable type ut_tfs_junit_reporter under ut_output_reporter_base( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/reporters/ut_xunit_reporter.tpb b/source/reporters/ut_xunit_reporter.tpb index eab0c8a3c..74fc57ac0 100644 --- a/source/reporters/ut_xunit_reporter.tpb +++ b/source/reporters/ut_xunit_reporter.tpb @@ -1,7 +1,7 @@ -create or replace type body ut_xunit_reporter is +create or replace noneditionable type body ut_xunit_reporter is /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. @@ -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/reporters/ut_xunit_reporter.tps b/source/reporters/ut_xunit_reporter.tps index 6c530fee4..d701d8ab0 100644 --- a/source/reporters/ut_xunit_reporter.tps +++ b/source/reporters/ut_xunit_reporter.tps @@ -1,7 +1,7 @@ -create or replace type ut_xunit_reporter under ut_junit_reporter( +create or replace noneditionable type ut_xunit_reporter under ut_junit_reporter( /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/set_install_params.sql b/source/set_install_params.sql index 34a23dc93..b559b8023 100644 --- a/source/set_install_params.sql +++ b/source/set_install_params.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/uninstall.sql b/source/uninstall.sql index ced7b8dc4..712845fb7 100644 --- a/source/uninstall.sql +++ b/source/uninstall.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/uninstall_all.sql b/source/uninstall_all.sql index cfaaaa29f..d3d3f3fbe 100644 --- a/source/uninstall_all.sql +++ b/source/uninstall_all.sql @@ -1,6 +1,6 @@ /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2026 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/uninstall_coverage_tables.sql b/source/uninstall_coverage_tables.sql index 890aa7090..df3c19627 100644 --- a/source/uninstall_coverage_tables.sql +++ b/source/uninstall_coverage_tables.sql @@ -1,3 +1,20 @@ +/* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. +*/ + set echo off set feedback off begin diff --git a/source/uninstall_objects.sql b/source/uninstall_objects.sql index ab4069497..fcc8dfa52 100644 --- a/source/uninstall_objects.sql +++ b/source/uninstall_objects.sql @@ -1,5 +1,21 @@ +/* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. +*/ + set echo off -set serverout on declare procedure drop_if_exists(a_object_type varchar2, a_object_name varchar2) is l_count integer; @@ -58,8 +74,6 @@ drop synonym be_within; drop synonym be_within_pct; -drop type ut_coveralls_reporter force; - drop type ut_coverage_sonar_reporter force; drop type ut_coverage_cobertura_reporter force; @@ -96,6 +110,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,8 +284,12 @@ end; drop package ut_utils; +drop type ut_stack force; + drop sequence ut_savepoint_seq; +drop type ut_tap_reporter force; + drop type ut_documentation_reporter force; drop type ut_debug_reporter force; @@ -324,12 +344,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..4a12340f6 100644 --- a/source/uninstall_synonyms.sql +++ b/source/uninstall_synonyms.sql @@ -1,3 +1,20 @@ +/* + utPLSQL - Version 3 + Copyright 2016 - 2026 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. +*/ + set echo off set feedback off declare @@ -15,10 +32,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_and_run_tests.sh b/test/install_and_run_tests.sh index 04f51fb3c..0c17b08b7 100755 --- a/test/install_and_run_tests.sh +++ b/test/install_and_run_tests.sh @@ -3,7 +3,8 @@ set -ev #goto git root directory git rev-parse && cd "$(git rev-parse --show-cdup)" +. ./development/env.sh cd test -time . ./${DIR}/install_tests.sh -time . ./${DIR}/run_tests.sh +time . ./install_tests.sh +time . ./run_tests.sh 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/install_ut3_user_tests.sql b/test/install_ut3_user_tests.sql index ad7f014bc..b74c5d7ae 100644 --- a/test/install_ut3_user_tests.sql +++ b/test/install_ut3_user_tests.sql @@ -43,13 +43,13 @@ set define off @@ut3_user/reporters/test_documentation_reporter.pks @@ut3_user/reporters/test_debug_reporter.pks @@ut3_user/reporters/test_realtime_reporter.pks +@@ut3_user/reporters/test_tap_reporter.pks @@ut3_user/reporters/test_coverage.pks @@ut3_user/reporters/test_coverage/test_coverage_standalone.pks set define on @@ut3_user/reporters/test_coverage/test_extended_coverage.pks @@ut3_user/reporters/test_coverage/test_html_coverage_reporter.pks set define off -@@ut3_user/reporters/test_coverage/test_coveralls_reporter.pks @@ut3_user/reporters/test_coverage/test_cov_cobertura_reporter.pks @@ut3_user/reporters/test_coverage/test_coverage_sonar_reporter.pks set define on @@ -86,12 +86,12 @@ set define off @@ut3_user/reporters/test_documentation_reporter.pkb @@ut3_user/reporters/test_debug_reporter.pkb @@ut3_user/reporters/test_realtime_reporter.pkb +@@ut3_user/reporters/test_tap_reporter.pkb @@ut3_user/reporters/test_coverage/test_coverage_standalone.pkb set define on @@ut3_user/reporters/test_coverage/test_extended_coverage.pkb @@ut3_user/reporters/test_coverage/test_html_coverage_reporter.pkb set define off -@@ut3_user/reporters/test_coverage/test_coveralls_reporter.pkb @@ut3_user/reporters/test_coverage/test_cov_cobertura_reporter.pkb @@ut3_user/reporters/test_coverage/test_coverage_sonar_reporter.pkb set define on diff --git a/test/run_tests.sh b/test/run_tests.sh index 118bab920..078d011d6 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -4,7 +4,9 @@ set -ev #goto git root directory git rev-parse && cd "$(git rev-parse --show-cdup)" -time utPLSQL-cli/bin/utplsql run UT3_TESTER_HELPER/ut3@${CONNECTION_STR} \ +export NLS_LANG=AMERICAN_AMERICA.AL32UTF8 + +time utPLSQL-cli/bin/utplsql run UT3_TESTER_HELPER/ut3@//${CONNECTION_STR} -D \ -source_path=source -owner=ut3_develop \ -p='ut3_tester,ut3_user' \ -test_path=test -c \ @@ -15,7 +17,6 @@ time utPLSQL-cli/bin/utplsql run UT3_TESTER_HELPER/ut3@${CONNECTION_STR} \ -f=ut_coverage_sonar_reporter -o=coverage.xml \ -f=ut_coverage_cobertura_reporter -o=cobertura.xml \ -f=ut_coverage_html_reporter -o=coverage.html \ --f=ut_coveralls_reporter -o=coverage.json \ -f=ut_sonar_test_reporter -o=test_results.xml \ -f=ut_junit_reporter -o=junit_test_results.xml \ -f=ut_tfs_junit_reporter -o=tfs_test_results.xml \ diff --git a/test/ut3_tester/core/annotations/test_annot_throws_exception.pkb b/test/ut3_tester/core/annotations/test_annot_throws_exception.pkb index ac51d3a49..761377765 100644 --- a/test/ut3_tester/core/annotations/test_annot_throws_exception.pkb +++ b/test/ut3_tester/core/annotations/test_annot_throws_exception.pkb @@ -1,6 +1,7 @@ create or replace package body test_annot_throws_exception is g_tests_results clob; + g_results_other_schema clob; procedure recollect_tests_results is pragma autonomous_transaction; @@ -25,10 +26,18 @@ is e_some_exception exception; pragma exception_init(e_some_exception, -20207); - end;]'; + end; + ]'; - l_package_spec := ' - create package annotated_package_with_throws is + l_package_spec := q'[ + create or replace package annotated_package_with_throws is + + c_local_error_no constant number := -20211; + e_some_local_exception exception; + pragma exception_init(e_some_local_exception, -20212); + + e_uninitialized_exception_variable exception; + --%suite(Dummy package to test annotation throws) --%test(Throws same annotated exception) @@ -95,6 +104,22 @@ is --%throws(exc_pkg.c_e_mix_missin,utter_rubbish) procedure mixed_list_notexi; + --%test(Success referencing an exception variable when running from another schema) + --%throws(exc_pkg.e_some_exception) + procedure named_exc_pragma_run_from_another_schema; + + --%test(Success referencing a numeric exception variable when running from another schema) + --%throws(exc_pkg.c_e_list_1) + procedure exc_number_var_run_from_another_schema; + + --%test(Success referencing an exception variable without package name when running from another schema) + --%throws(e_some_local_exception) + procedure named_exc_pragma_run_from_another_schema_no_package_name; + + --%test(Success referencing a numeric exception variable without package name when running from another schema) + --%throws(c_local_error_no) + procedure exc_number_var_run_from_another_schema_no_package_name; + --%test(Success resolve and match named exception defined in pragma exception init) --%throws(exc_pkg.e_some_exception) procedure named_exc_pragma; @@ -122,11 +147,20 @@ is --%test(Bad exception constant) --%throws(exc_pkg.c_e_dummy); procedure bad_exc_const; + + --%test(Uninitialized exception variable can be used successfully in a test) + --%throws(e_uninitialized_exception_variable) + procedure referencing_uninitialized_exception; + + --%test(Failure report shows all expected exceptions) + --%throws(c_local_error_no,e_some_local_exception,e_uninitialized_exception_variable) + procedure not_throwing_expected_exceptions; + end; - '; + ]'; l_package_body := ' - create package body annotated_package_with_throws is + create or replace package body annotated_package_with_throws is procedure raised_same_exception is begin raise_application_error(-20145, ''Test error''); @@ -208,6 +242,26 @@ is raise_application_error(exc_pkg.c_e_mix_missin,''Test''); end; + procedure named_exc_pragma_run_from_another_schema is + begin + raise exc_pkg.e_some_exception; + end; + + procedure exc_number_var_run_from_another_schema is + begin + raise_application_error(exc_pkg.c_e_list_1,''Test''); + end; + + procedure named_exc_pragma_run_from_another_schema_no_package_name is + begin + raise e_some_local_exception; + end; + + procedure exc_number_var_run_from_another_schema_no_package_name is + begin + raise_application_error(c_local_error_no,''Test''); + end; + procedure named_exc_pragma is begin raise exc_pkg.e_some_exception; @@ -243,6 +297,15 @@ is raise_application_error(-20143, ''Test error''); end; + procedure referencing_uninitialized_exception is + begin + raise e_uninitialized_exception_variable; + end; + + procedure not_throwing_expected_exceptions is + begin + raise_application_error(-20143, ''Test error''); + end; end; '; @@ -256,6 +319,14 @@ is g_tests_results := ut3_develop.ut_utils.table_to_clob(l_test_results); end; + procedure drop_test_package is + pragma autonomous_transaction; + begin + execute immediate 'drop package annotated_package_with_throws'; + execute immediate 'drop package exc_pkg'; + end; + + procedure throws_same_annotated_except is begin ut.expect(g_tests_results).to_match('^\s*Throws same annotated exception \[[,\.0-9]+ sec\]\s*$','m'); @@ -312,7 +383,7 @@ is procedure one_valid_exception_number is begin - ut.expect(g_tests_results).to_match('^\s*Detects a valid exception number within many invalid ones \[[\.0-9]+ sec\]\s*$','m'); + ut.expect(g_tests_results).to_match('^\s*Detects a valid exception number within many invalid ones \[[,\.0-9]+ sec\]\s*$','m'); ut.expect(g_tests_results).to_match('one_valid_exception_number\s*Invalid parameter value ".*" for "--%throws" annotation. Parameter ignored.','m'); end; @@ -357,7 +428,37 @@ is ut.expect(g_tests_results).to_match('^\s*Success resolve and match named exception defined in pragma exception init \[[,\.0-9]+ sec\]\s*$','m'); ut.expect(g_tests_results).not_to_match('named_exc_pragma'); end; - + + procedure run_from_another_schema is + begin + ut3_tester_helper.run_helper.run(('ut3_tester.annotated_package_with_throws')); + g_results_other_schema := ut3_tester_helper.main_helper.get_dbms_output_as_clob(); + end; + + procedure named_exc_pragma_run_from_another_schema is + begin + ut.expect(g_results_other_schema).to_match('^\s*Success referencing an exception variable when running from another schema \[[,\.0-9]+ sec\]\s*$', 'm'); + ut.expect(g_results_other_schema).not_to_match('named_exc_pragma_run_from_another_schema'); + end; + + procedure named_exc_pragma_run_from_another_schema_no_package_name is + begin + ut.expect(g_results_other_schema).to_match('^\s*Success referencing an exception variable without package name when running from another schema \[[,\.0-9]+ sec\]\s*$', 'm'); + ut.expect(g_results_other_schema).not_to_match('named_exc_pragma_run_from_another_schema_no_package_name'); + end; + + procedure exc_number_var_run_from_another_schema_no_package_name is + begin + ut.expect(g_results_other_schema).to_match('^\s*Success referencing a numeric exception variable without package name when running from another schema \[[,\.0-9]+ sec\]\s*$', 'm'); + ut.expect(g_results_other_schema).not_to_match('exc_number_var_run_from_another_schema_no_package_name'); + end; + + procedure exc_number_var_run_from_another_schema is + begin + ut.expect(g_results_other_schema).to_match('^\s*Success referencing a numeric exception variable when running from another schema \[[,\.0-9]+ sec\]\s*$', 'm'); + ut.expect(g_results_other_schema).not_to_match('exc_number_var_run_from_another_schema'); + end; + procedure named_exc_ora is begin ut.expect(g_tests_results).to_match('^\s*Success resolve and match oracle named exception \[[,\.0-9]+ sec\]\s*$','m'); @@ -392,13 +493,23 @@ is begin ut.expect(g_tests_results).to_match('^\s*Bad exception constant \[[,\.0-9]+ sec\] \(FAILED - [0-9]+\)\s*$','m'); ut.expect(g_tests_results).to_match('bad_exc_const\s*ORA-20143: Test error\s*ORA-06512: at "UT3_TESTER.ANNOTATED_PACKAGE_WITH_THROWS"'); - end; - - procedure drop_test_package is - pragma autonomous_transaction; + end; + + procedure referencing_uninitialized_exception is + l_test_results ut3_develop.ut_varchar2_list; + l_actual clob; begin - execute immediate 'drop package annotated_package_with_throws'; - execute immediate 'drop package exc_pkg'; + select * bulk collect into l_test_results from table(ut3_develop.ut.run(('annotated_package_with_throws.referencing_uninitialized_exception'))); + + l_actual := ut3_develop.ut_utils.table_to_clob(l_test_results); + ut.expect(l_actual).to_match('^\s*Uninitialized exception variable can be used successfully in a test \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect(l_actual).not_to_match('referencing_uninitialized_exception'); + end; + + procedure not_throwing_expected_exceptions is + begin + ut.expect(g_tests_results).to_match('not_throwing_expected_exceptions'); + ut.expect(g_tests_results).to_match('Actual: -20143 was expected to be one of: \(annotated_package_with_throws.e_some_local_exception, annotated_package_with_throws.e_uninitialized_exception_variable, -20211\)'); end; end; diff --git a/test/ut3_tester/core/annotations/test_annot_throws_exception.pks b/test/ut3_tester/core/annotations/test_annot_throws_exception.pks index a9d20183d..a41c18d2b 100644 --- a/test/ut3_tester/core/annotations/test_annot_throws_exception.pks +++ b/test/ut3_tester/core/annotations/test_annot_throws_exception.pks @@ -5,7 +5,11 @@ is --%beforeall procedure recollect_tests_results; - + + --%afterall + procedure drop_test_package; + + --%test(Gives success when annotated number exception is thrown) procedure throws_same_annotated_except; @@ -18,7 +22,7 @@ is --%test(Gives failure when the raised exception is different that the annotated one) procedure throws_diff_annotated_except; - --%test(Ignores when the annotation throws is empty) + --%test(Ignores the throws annotation when it is empty) procedure throws_empty; --%test(Ignores when only bad parameters are passed, the test raise a exception and it shows errored test) @@ -36,7 +40,7 @@ is --%test(Detects a valid exception number within many invalid ones) procedure one_valid_exception_number; - --%test(Gives failure when a exception is expected and nothing is thrown) + --%test(Gives failure when an exception is expected and nothing is thrown) procedure nothing_thrown; --%test(Single exception defined as a constant number in package) @@ -45,19 +49,19 @@ is --%test(Gives success when one of annotated exception using constant is thrown) procedure list_of_exc_constant; - --%test(Gives failure when the raised exception is different that the annotated one using variable) + --%test(Gives failure when the raised exception is different than the annotated one using variable) procedure fail_not_match_exc; - --%test(Success when one of exception from mixed list of number and constant is thrown) + --%test(Success when one of exception from a mixed list of number and constant is thrown) procedure mixed_exc_list; - --%test(Success when match exception even if other variable on list dont exists) + --%test(Success when match exception even if another variable on list doesn't exists) procedure mixed_list_notexi; --%test(Success resolve and match named exception defined in pragma exception init) procedure named_exc_pragma; - - --%test(Success resolve and match oracle named exception no data) + + --%test(Success resolve and match oracle named exception no data) procedure named_exc_ora; --%test(Success resolve and match oracle named exception dup val index) @@ -73,10 +77,32 @@ is procedure non_existing_const; --%test(Bad exception constant) - procedure bad_exc_const; + procedure bad_exc_const; + + --%test(Uninitialized exception variable can be used successfully in a test) + procedure referencing_uninitialized_exception; + + --%test(a Failure report shows all expected exceptions) + procedure not_throwing_expected_exceptions; - --%afterall - procedure drop_test_package; + --%context(referencing exceptions when running from another schema) + + --%beforeall + procedure run_from_another_schema; + + --%test(Success referencing an exception variable when running from another schema) + procedure named_exc_pragma_run_from_another_schema; + + --%test(Success referencing a numeric exception variable when running from another schema) + procedure exc_number_var_run_from_another_schema; + + --%test(Success referencing an exception variable without a package name when running from another schema) + procedure named_exc_pragma_run_from_another_schema_no_package_name; + + --%test(Success referencing a numeric exception variable without a package name when running from another schema) + procedure exc_number_var_run_from_another_schema_no_package_name; + + --%endcontext end; / diff --git a/test/ut3_tester/core/annotations/test_annotation_parser.pkb b/test/ut3_tester/core/annotations/test_annotation_parser.pkb index bc789c377..beb526863 100644 --- a/test/ut3_tester/core/annotations/test_annotation_parser.pkb +++ b/test/ut3_tester/core/annotations/test_annotation_parser.pkb @@ -1,27 +1,28 @@ create or replace package body test_annotation_parser is procedure test_proc_comments is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; - begin - l_source := 'PACKAGE test_tt AS - -- %suite - -- %displayname(Name of suite) - -- %suitepath(all.globaltests) - - -- %ann1(Name of suite) - -- wrong line - -- %ann2(some_value) - procedure foo; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(Name of suite)' || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + '' || chr(10), + ' -- %ann1(Name of suite)' || chr(10), + ' -- wrong line' || chr(10), + ' -- %ann2(some_value)' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert - l_expected := ut3_develop.ut_annotations( ut3_develop.ut_annotation(2,'suite',null, null), ut3_develop.ut_annotation(3,'displayname','Name of suite',null), @@ -34,30 +35,33 @@ create or replace package body test_annotation_parser is end; procedure include_floating_annotations is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - -- %suite - -- %displayname(Name of suite) - -- %suitepath(all.globaltests) - - -- %ann1(Name of suite) - -- %ann2(all.globaltests) - - --%test - procedure foo; - - -- %ann3(Name of suite) - -- %ann4(all.globaltests) - - --%test - procedure bar; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(Name of suite)' || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + '' || chr(10), + ' -- %ann1(Name of suite)' || chr(10), + ' -- %ann2(all.globaltests)' || chr(10), + '' || chr(10), + ' --%test' || chr(10), + ' procedure foo;' || chr(10), + '' || chr(10), + ' -- %ann3(Name of suite)' || chr(10), + ' -- %ann4(all.globaltests)' || chr(10), + '' || chr(10), + ' --%test' || chr(10), + ' procedure bar;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -77,40 +81,41 @@ create or replace package body test_annotation_parser is end; procedure parse_complex_with_functions is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - -- %suite - -- %displayname(Name of suite) - -- %suitepath(all.globaltests) - - --%test - procedure foo; - - - --%beforeeach - procedure foo2; - - --test comment - -- wrong comment - - - /* - describtion of the procedure - */ - --%beforeeach(key=testval) - PROCEDURE foo3(a_value number default null); - - --%all - function foo4(a_val number default null - , a_par varchar2 default := ''asdf''); - END;'; + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(Name of suite)' || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + '' || chr(10), + ' --%test' || chr(10), + ' procedure foo;' || chr(10), + '' || chr(10), + '' || chr(10), + ' --%beforeeach' || chr(10), + ' procedure foo2;' || chr(10), + '' || chr(10), + ' --test comment' || chr(10), + ' -- wrong comment' || chr(10), + '' || chr(10), + '' || chr(10), + '/*' || chr(10), + ' describtion of the procedure' || chr(10), + ' */' || chr(10), + ' --%beforeeach(key=testval)' || chr(10), + ' PROCEDURE foo3(a_value number default null);' || chr(10), + '' || chr(10), + ' --%all' || chr(10), + ' function foo4(a_val number default null' || chr(10), + ' , a_par varchar2 default := ''asdf'');' || chr(10), + 'END;')); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -128,21 +133,24 @@ create or replace package body test_annotation_parser is end; procedure no_procedure_annotation is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - -- %suite - -- %displayname(Name of suite) - -- %suitepath(all.globaltests) - - procedure foo; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(Name of suite)' || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + '' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -156,21 +164,24 @@ create or replace package body test_annotation_parser is end; procedure parse_accessible_by is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt accessible by (foo) AS - -- %suite - -- %displayname(Name of suite) - -- %suitepath(all.globaltests) - - procedure foo; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt accessible by (foo) AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(Name of suite)' || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + '' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -184,24 +195,27 @@ create or replace package body test_annotation_parser is end; procedure complex_package_declaration is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt - ACCESSIBLE BY (calling_proc) - authid current_user - AS - -- %suite - -- %displayname(Name of suite) - -- %suitepath(all.globaltests) - - procedure foo; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt' || chr(10), + ' ACCESSIBLE BY (calling_proc)' || chr(10), + ' authid current_user' || chr(10), + ' AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(Name of suite)' || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + '' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -215,21 +229,24 @@ create or replace package body test_annotation_parser is end; procedure complex_text is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - -- %suite - --%displayname(name = Name of suite) - -- %suitepath(key=all.globaltests,key2=foo,"--%some text") - - procedure foo; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' --%displayname(name = Name of suite)' || chr(10), + ' -- %suitepath(key=all.globaltests,key2=foo,"--%some text")' || chr(10), + '' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -243,26 +260,29 @@ create or replace package body test_annotation_parser is end; procedure ignore_annotations_in_comments is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - /* - Some comment - -- inlined - -- %ignored - */ - -- %suite - --%displayname(Name of suite) - -- %suitepath(all.globaltests) - - procedure foo; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' /*' || chr(10), + ' Some comment' || chr(10), + ' -- inlined' || chr(10), + ' -- %ignored' || chr(10), + ' */' || chr(10), + ' -- %suite' || chr(10), + ' --%displayname(Name of suite)' || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + '' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -311,17 +331,19 @@ v58yvbLAXLi9gYHwoIvAgccti+Cmpg0DKLY= end; procedure brackets_in_desc is - - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - -- %suite(Name of suite (including some brackets) and some more text) -END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite(Name of suite (including some brackets) and some more text)' || chr(10), + 'END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -332,24 +354,27 @@ END;'; end; procedure test_space_before_annot_params is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; - l_expected ut3_develop.ut_annotations; + l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - /* - Some comment - -- inlined - */ - -- %suite - -- %suitepath (all.globaltests) - - procedure foo; -END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' /*' || chr(10), + ' Some comment' || chr(10), + ' -- inlined' || chr(10), + ' */' || chr(10), + ' -- %suite' || chr(10), + ' -- %suitepath (all.globaltests)'|| chr(10), + '' || chr(10), + ' procedure foo;' || chr(10), + 'END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -362,18 +387,21 @@ END;'; procedure test_windows_newline as - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - -- %suite - -- %displayname(Name of suite)' || chr(13) || chr(10) - || ' -- %suitepath(all.globaltests) - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(Name of suite)' || chr(13) || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -387,21 +415,24 @@ END;'; procedure test_annot_very_long_name as - l_source clob; - l_actual ut3_develop.ut_annotations; - l_expected ut3_develop.ut_annotations; + l_source dbms_preprocessor.source_lines_t; + l_actual ut3_develop.ut_annotations; + l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE very_long_procedure_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_did_it AS - -- %suite - -- %displayname(Name of suite) - -- %suitepath(all.globaltests) - - --%test - procedure very_long_procedure_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_dit_it; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE very_long_procedure_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_did_it AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(Name of suite)' || chr(10), + ' -- %suitepath(all.globaltests)' || chr(10), + '' || chr(10), + ' --%test' || chr(10), + ' procedure very_long_procedure_name_valid_for_oracle_12_so_utPLSQL_should_allow_it_definitely_well_still_not_reached_128_but_wait_we_dit_it;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -415,30 +446,33 @@ END;'; end; procedure test_upper_annot is - l_source clob; + l_source dbms_preprocessor.source_lines_t; l_actual ut3_develop.ut_annotations; l_expected ut3_develop.ut_annotations; begin - l_source := 'PACKAGE test_tt AS - -- %SUITE - -- %DISPLAYNAME(Name of suite) - -- %SUITEPATH(all.globaltests) - - -- %ANN1(Name of suite) - -- %ANN2(all.globaltests) - - --%TEST - procedure foo; - - -- %ANN3(Name of suite) - -- %ANN4(all.globaltests) - - --%TEST - procedure bar; - END;'; + --Arrange + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %SUITE' || chr(10), + ' -- %DISPLAYNAME(Name of suite)' || chr(10), + ' -- %SUITEPATH(all.globaltests)' || chr(10), + '' || chr(10), + ' -- %ANN1(Name of suite)' || chr(10), + ' -- %ANN2(all.globaltests)' || chr(10), + '' || chr(10), + ' --%TEST' || chr(10), + ' procedure foo;' || chr(10), + '' || chr(10), + ' -- %ANN3(Name of suite)' || chr(10), + ' -- %ANN4(all.globaltests)' || chr(10), + '' || chr(10), + ' --%TEST' || chr(10), + ' procedure bar;' || chr(10), + ' END;' || chr(10) + )); --Act - l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source); + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); --Assert l_expected := ut3_develop.ut_annotations( @@ -457,5 +491,414 @@ END;'; end; + ------------------------------------------------------------ + -- replace_multiline_comments coverage tests + ------------------------------------------------------------ + + procedure test_rmc_empty_source is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result.count).to_equal(0); + end; + + procedure test_rmc_no_ml_comment_marker is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'procedure foo is' || chr(10), + 'begin' || chr(10), + ' null;' || chr(10), + 'end;' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(ut3_tester_helper.main_helper.lines_to_str(l_result)).to_equal(ut3_tester_helper.main_helper.lines_to_str(l_input)); + end; + + procedure test_rmc_line_inside_ml_comment is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'x := 1; /* start' || chr(10), + 'this whole line is comment' || chr(10), + 'end comment */ x := 2;' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal('x := 1; ' || chr(10)); + ut.expect(l_result(2)).to_equal(''); + ut.expect(l_result(3)).to_equal(' x := 2;' || chr(10)); + end; + + procedure test_rmc_ml_comment_closed_midline is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + '/* open' || chr(10), + 'still inside' || chr(10), + '*/ code /* remove this too */ kept' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(2)).to_equal(''); + ut.expect(l_result(3)).to_equal(' code kept' || chr(10)); + end; + + procedure test_rmc_fast_path_no_tokens is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + -- line 2 has none of / - ' so hits fast path B; line 1 needed to pass pre-scan + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + '/* comment */' || chr(10), + 'begin' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(2)).to_equal('begin' || chr(10)); + end; + + procedure test_rmc_ml_comment_single_line is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'x := /* inline comment */ 42;' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal('x := 42;' || chr(10)); + end; + + procedure test_rmc_single_line_comment_preserved is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + '/* marker */' || chr(10), + ' -- %test annotation' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(2)).to_equal(' -- %test annotation' || chr(10)); + end; + + procedure test_rmc_string_literal_protects_markers is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'v := ''val /* not a comment */ here'';' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal('v := ''val /* not a comment */ here'';' || chr(10)); + end; + + procedure test_rmc_string_literal_escaped_quotes is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'v := ''it''''s a /* test */'';' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal('v := ''it''''s a /* test */'';' || chr(10)); + end; + + procedure test_rmc_q_quoted_string_literal is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'v := q''[/* not a comment */]'';' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal('v := q''[/* not a comment */]'';' || chr(10)); + end; + + procedure test_rmc_unclosed_string_literal is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'v := ''hello /* inside unclosed' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal('v := ''hello /* inside unclosed' || chr(10)); + end; + + procedure test_rmc_unclosed_q_string is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + c_line constant varchar2(100) := q'(v := q'[/* unclosed q-string)' || chr(10); + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list(c_line)); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal(c_line); + end; + + procedure test_rmc_multiple_ml_comments_one_line is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'a /* one */ := /* two */ 1;' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal('a := 1;' || chr(10)); + end; + + procedure test_rmc_comment_after_string_with_slash is + l_input dbms_preprocessor.source_lines_t; + l_result dbms_preprocessor.source_lines_t; + begin + --Arrange + l_input := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'v := ''a/b''; -- this is /* not */ a ml comment' || chr(10) + )); + + --Act + l_result := ut3_develop.ut_utils.replace_multiline_comments(l_input); + + --Assert + ut.expect(l_result(1)).to_equal('v := ''a/b''; -- this is /* not */ a ml comment' || chr(10)); + end; + + procedure test_multiline_proc_header_lines is + l_source dbms_preprocessor.source_lines_t; + l_actual ut3_develop.ut_annotations; + l_expected ut3_develop.ut_annotations; + begin + --Arrange + -- procedure header spans multiple lines before terminating ; + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + '' || chr(10), + ' --%test' || chr(10), + ' procedure foo(' || chr(10), + ' a_param1 varchar2' || chr(10), + ' ,a_param2 number default null' || chr(10), + ' );' || chr(10), + ' END;' || chr(10) + )); + + --Act + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); + + --Assert + l_expected := ut3_develop.ut_annotations( + ut3_develop.ut_annotation( 2, 'suite', null, null ), + ut3_develop.ut_annotation( 4, 'test', null, 'foo' ) + ); + + ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)); + end; + + procedure test_non_comment_line_resets_block_lines is + l_source dbms_preprocessor.source_lines_t; + l_actual ut3_develop.ut_annotations; + l_expected ut3_develop.ut_annotations; + begin + --Arrange + -- a non-comment non-proc line between annotation and procedure breaks the association + -- %test becomes a floating top-level annotation with no subobject + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + '' || chr(10), + ' --%test' || chr(10), + ' pragma serially_reusable;' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); + + --Act + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); + + --Assert + -- %test is NOT associated with foo — accumulator was reset by pragma line + -- it surfaces as a top-level annotation with no subobject_name + l_expected := ut3_develop.ut_annotations( + ut3_develop.ut_annotation( 2, 'suite', null, null ), + ut3_develop.ut_annotation( 4, 'test', null, null ) + ); + + ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)); + end; + + procedure test_annotation_not_at_line_start_ignored_lines is + l_source dbms_preprocessor.source_lines_t; + l_actual ut3_develop.ut_annotations; + l_expected ut3_develop.ut_annotations; + begin + --Arrange + -- code before -- means it is not at start of line and must be ignored + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' x := 1; -- %not_an_annotation' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); + + --Act + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); + + --Assert + l_expected := ut3_develop.ut_annotations( + ut3_develop.ut_annotation( 2, 'suite', null, null ) + ); + + ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)); + end; + + procedure test_whitespace_line_between_comment_and_proc_lines is + l_source dbms_preprocessor.source_lines_t; + l_actual ut3_develop.ut_annotations; + l_expected ut3_develop.ut_annotations; + begin + --Arrange + -- space-only line between annotation comment and procedure declaration is tolerated + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + '' || chr(10), + ' --%test' || chr(10), + ' ' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); + + --Act + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); + + --Assert + l_expected := ut3_develop.ut_annotations( + ut3_develop.ut_annotation( 2, 'suite', null, null ), + ut3_develop.ut_annotation( 4, 'test', null, 'foo' ) + ); + + ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)); + end; + + procedure test_space_between_dashes_and_qualifier_lines is + l_source dbms_preprocessor.source_lines_t; + l_actual ut3_develop.ut_annotations; + l_expected ut3_develop.ut_annotations; + begin + --Arrange + -- spaces between -- and % are skipped and annotation is still recognised + -- annotations are not adjacent to a procedure so they are top-level + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' -- %displayname(my suite)' || chr(10), + '' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); + + --Act + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); + + --Assert + l_expected := ut3_develop.ut_annotations( + ut3_develop.ut_annotation( 2, 'suite', null, null ), + ut3_develop.ut_annotation( 3, 'displayname', 'my suite', null ) + ); + + ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)); + end; + + procedure test_percent_not_after_dashes_ignored_lines is + l_source dbms_preprocessor.source_lines_t; + l_actual ut3_develop.ut_annotations; + l_expected ut3_develop.ut_annotations; + begin + --Arrange + -- -- present and % present on line but % is not immediately after -- + -- e.g. a regular comment that happens to mention 100% + l_source := ut3_tester_helper.main_helper.make_source(ut3_develop.ut_varchar2_list( + 'PACKAGE test_tt AS' || chr(10), + ' -- %suite' || chr(10), + ' -- coverage is 100% on this module' || chr(10), + ' procedure foo;' || chr(10), + ' END;' || chr(10) + )); + + --Act + l_actual := ut3_develop.ut_annotation_parser.parse_object_annotations(l_source, 'PACKAGE'); + + --Assert + l_expected := ut3_develop.ut_annotations( + ut3_develop.ut_annotation( 2, 'suite', null, null ) + ); + + ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)); + end; + end test_annotation_parser; -/ +/ \ No newline at end of file diff --git a/test/ut3_tester/core/annotations/test_annotation_parser.pks b/test/ut3_tester/core/annotations/test_annotation_parser.pks index a4fe3ed70..c61c14d3a 100644 --- a/test/ut3_tester/core/annotations/test_annotation_parser.pks +++ b/test/ut3_tester/core/annotations/test_annotation_parser.pks @@ -19,24 +19,57 @@ create or replace package test_annotation_parser is procedure complex_text; --%test(Ignores content of multi-line comments) procedure ignore_annotations_in_comments; - - --%test(Ignores wrapped package and does not raise exception) + -- %test(ignore wrapped package source) procedure ignore_wrapped_package; - --%test(Parses package level annotations with annotation params containing brackets) procedure brackets_in_desc; - --%test(Parses annotation text even with spaces before brackets) procedure test_space_before_annot_params; - -- %test(Parses source-code with Windows-style newline) procedure test_windows_newline; - - -- %test(Parses annotations with very long object names) + -- %test(parse annotations with very long procedure name) procedure test_annot_very_long_name; - -- %test(Parses upper case annotations) procedure test_upper_annot; + -- %test(empty source returns immediately without processing) + procedure test_rmc_empty_source; + -- %test(source with no multiline comment markers returned unchanged) + procedure test_rmc_no_ml_comment_marker; + -- %test(line entirely inside multiline comment is blanked) + procedure test_rmc_line_inside_ml_comment; + -- %test(closing delimiter mid-line falls through to scan remainder) + procedure test_rmc_ml_comment_closed_midline; + -- %test(fast path line with no slash dash or quote copied unchanged) + procedure test_rmc_fast_path_no_tokens; + -- %test(single-line block comment removed on same line) + procedure test_rmc_ml_comment_single_line; + -- %test(single-line comment preserved intact) + procedure test_rmc_single_line_comment_preserved; + -- %test(string literal containing comment markers not stripped) + procedure test_rmc_string_literal_protects_markers; + -- %test(string literal with escaped quotes handled correctly) + procedure test_rmc_string_literal_escaped_quotes; + -- %test(q-quoted string literal protects interior comment markers) + procedure test_rmc_q_quoted_string_literal; + -- %test(unclosed string literal copies remainder and exits scan) + procedure test_rmc_unclosed_string_literal; + -- %test(unclosed q-quoted string copies remainder and exits scan) + procedure test_rmc_unclosed_q_string; + -- %test(multiple block comments on single line all removed) + procedure test_rmc_multiple_ml_comments_one_line; + -- %test(comment markers after string with slash handled correctly) + procedure test_rmc_comment_after_string_with_slash; + --%test(Procedure header spanning multiple lines is handled correctly) + procedure test_multiline_proc_header_lines; + --%test(Non-comment line between annotation and procedure resets association) + procedure test_non_comment_line_resets_block_lines; + --%test(Comment with non-whitespace before dashes is not treated as annotation) + procedure test_annotation_not_at_line_start_ignored_lines; + --%test(Spaces between dashes and annotation qualifier are skipped correctly) + procedure test_space_between_dashes_and_qualifier_lines; + --%test(Percent sign not immediately after dashes is not treated as annotation) + procedure test_percent_not_after_dashes_ignored_lines; + end test_annotation_parser; -/ +/ \ No newline at end of file diff --git a/test/ut3_tester/core/test_output_buffer.pkb b/test/ut3_tester/core/test_output_buffer.pkb index 2e8b3337c..55670b8b8 100644 --- a/test/ut3_tester/core/test_output_buffer.pkb +++ b/test/ut3_tester/core/test_output_buffer.pkb @@ -1,5 +1,13 @@ create or replace package body test_output_buffer is + function long_text return varchar2 is + l_txt varchar2(32767); + begin + for i in 1 .. 100 loop + l_txt := l_txt||lpad('a text', 310, ',a text'); + end loop; + return l_txt; + end; procedure test_receive is l_actual_text clob; l_actual_item_type varchar2(1000); @@ -10,18 +18,19 @@ create or replace package body test_output_buffer is begin --Arrange l_buffer := ut3_develop.ut_output_clob_table_buffer(); - l_expected_text := to_clob(lpad('a text', 31000, ',a text')) - || chr(10) || to_clob(lpad('a text', 31000, ',a text')) - || chr(13) || to_clob(lpad('a text', 31000, ',a text')) - || chr(13) || chr(10) || to_clob(lpad('a text', 31000, ',a text')) || to_clob(lpad('a text', 31000, ',a text')); + l_expected_text := to_clob('a text') ||long_text() + || chr(10) || long_text() + || chr(13) || long_text() + || chr(13) || chr(10) || long_text() || long_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 +41,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 +64,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 +75,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 +105,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 +121,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 +136,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 +153,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 +168,50 @@ create or replace package body test_output_buffer is test_purge(ut3_develop.ut_output_clob_table_buffer()); end; + 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; + + procedure concurrent_calls_do_not_cause_exception is + l_consumer_buffer_instance ut3_develop.ut_output_buffer_base; + l_shared_output_id raw(32) := sys_guid(); + begin + ut3_tester_helper.run_helper.run_as_job('declare x ut3_develop.ut_output_buffer_base; begin x := ut3_develop.ut_output_table_buffer(a_output_id => '''||l_shared_output_id||'''); end;'); + dbms_session.sleep(0.5); + l_consumer_buffer_instance := ut3_develop.ut_output_table_buffer(a_output_id => l_shared_output_id); + 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..aa5c830d3 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,21 @@ 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; + + --%beforetest(ut3_tester_helper.run_helper.slow_down_insert_into_table_buffer) + --%aftertest(ut3_tester_helper.run_helper.cleanup_slow_down_insert_into_table_buffer) + --%test(concurrent calls to init() procedure do not cause errors - Issue #985) + procedure concurrent_calls_do_not_cause_exception; + end test_output_buffer; / diff --git a/test/ut3_tester/core/test_suite_builder.pkb b/test/ut3_tester/core/test_suite_builder.pkb index 570d86a0d..9a86772c7 100644 --- a/test/ut3_tester/core/test_suite_builder.pkb +++ b/test/ut3_tester/core/test_suite_builder.pkb @@ -1370,6 +1370,54 @@ create or replace package body test_suite_builder is ); end; + procedure context_name_case_insensitive is + l_actual clob; + l_annotations ut3_develop.ut_annotations; + begin + --Arrange + l_annotations := ut3_develop.ut_annotations( + ut3_develop.ut_annotation(1, 'suite','Cool', null), + ut3_develop.ut_annotation(2, 'beforeall',null, 'suite_level_beforeall'), + ut3_develop.ut_annotation(3, 'test','In suite', 'suite_level_test'), + ut3_develop.ut_annotation(4, 'context','A context', null), + ut3_develop.ut_annotation(5, 'name','a_Context', null), + ut3_develop.ut_annotation(6, 'beforeall',null, 'context_setup'), + ut3_develop.ut_annotation(7, 'test', 'In context', 'test_in_a_context'), + ut3_develop.ut_annotation(8, 'endcontext',null, null) + ); + --Act + l_actual := invoke_builder_for_annotations(l_annotations, 'SOME_PACKAGE'); + --Assert + ut.expect(l_actual).to_be_like( + ''|| + '' || + '%' || + '%' || + '' || + '%suite_level_testIn suitesome_package.suite_level_test' || + '%' || + '' || + '%a_contextA contextsome_package.a_context' || + '%' || + '' || + '%test_in_a_contextIn contextsome_package.a_context.test_in_a_context' || + '%' || + '' || + '' || + '%some_packagecontext_setup' || + '%' || + '' || + '' || + '' || + '' || + '%some_packagesuite_level_beforeall' || + '%' || + '' || + ''|| + '' + ); + end; + procedure throws_value_empty is l_actual clob; l_annotations ut3_develop.ut_annotations; diff --git a/test/ut3_tester/core/test_suite_builder.pks b/test/ut3_tester/core/test_suite_builder.pks index 4cbfcad74..09ff3c0b3 100644 --- a/test/ut3_tester/core/test_suite_builder.pks +++ b/test/ut3_tester/core/test_suite_builder.pks @@ -142,7 +142,7 @@ create or replace package test_suite_builder is --%test(Falls back to default context name and gives warning when name contains spaces) procedure name_with_spaces_invalid; - --%test(Raises warning when more than one name annotation used ) + --%test(Raises warning when more than one name annotation used) procedure duplicate_name_annotation; --%test(Is ignored when used outside of context - no warning given) @@ -154,6 +154,9 @@ create or replace package test_suite_builder is --%test(Is applied to corresponding context when multiple contexts used) procedure multiple_contexts; + --%test(Context name is case insensitive) + procedure context_name_case_insensitive; + --%endcontext --%context(--%throws annotation) 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..44f102716 100644 --- a/test/ut3_tester/core/test_suite_manager.pks +++ b/test/ut3_tester/core/test_suite_manager.pks @@ -163,7 +163,7 @@ create or replace package test_suite_manager is --%context(get_schema_ut_packages) - --%test(returns list of all unit test packages in given schema) + --%test(returns list of all unit test packages in given schema excluding packages that are not suites) --%beforetest(create_ut3_suite) --%aftertest(drop_ut3_suite) procedure test_get_schema_ut_packages; @@ -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..32610edf6 --- /dev/null +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb @@ -0,0 +1,123 @@ +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 conversion_throws_when_lb_rb 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 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; + + procedure filtering_is_correctly_applied_to_all_items is + l_input_data ut3_develop.ut_suite_cache_rows; + l_expected sys_refcursor; + l_actual sys_refcursor; + begin + l_input_data := ut3_develop.ut_suite_cache_rows( + ut3_develop.ut_suite_cache_row( + 1, 'UT_SUITE', 'some.path.test_tags', 'TEST_USER', 'TEST_TAGS', 'TEST_TAGS', 3, timestamp '2026-05-06 10:23:38.461299', NULL, NULL, 0, NULL, null, null, null, null, null, null, null, null, + ut3_develop.ut_varchar2_rows('slow'), null), + ut3_develop.ut_suite_cache_row( + 2, 'UT_SUITE_CONTEXT', 'some.path.test_tags.nested_context_#1', 'TEST_USER', 'TEST_TAGS', 'NESTED_CONTEXT_#1', 9, timestamp '2026-05-06 10:23:38.461299', 'tags_are_applied_in_contexts', NULL, 0, NULL, null, null, null, null, null, null, null, null, + ut3_develop.ut_varchar2_rows('context_tag'), null), + ut3_develop.ut_suite_cache_row( + 3, 'UT_TEST', 'some.path.test_tags.nested_context_#1.create_new_mapping', 'TEST_USER', 'TEST_TAGS', 'CREATE_NEW_MAPPING', 11, timestamp '2026-05-06 10:23:38.461299', NULL, NULL, 0, NULL, null, null, null, null, null, null, null, null, + ut3_develop.ut_varchar2_rows('test_tag'), ut3_develop.ut_executable_test('UT_EXECUTABLE_TEST', 'test', 'TEST_USER', 'test_tags', 'create_new_mapping', NULL, NULL, NULL, NULL, NULL)) + ); + + ut.expect(ut3_develop.ut_suite_tag_filter.apply( l_input_data, 'slow').count).to_equal(3 ); + + open l_expected for select * from table( l_input_data ); + open l_actual for select * from table( ut3_develop.ut_suite_tag_filter.apply( l_input_data, 'slow') ); + ut.expect(l_actual).to_equal(l_expected).join_by('ID'); + + ut.expect( ut3_develop.ut_suite_tag_filter.apply(l_input_data, '!slow').count ).to_equal(0); + ut.expect( ut3_develop.ut_suite_tag_filter.apply(l_input_data, 'bad').count ).to_equal(0); + ut.expect( ut3_develop.ut_suite_tag_filter.apply(l_input_data, '!bad').count ).to_equal(3); + ut.expect( ut3_develop.ut_suite_tag_filter.apply(l_input_data, '!context_tag').count ).to_equal(0); + ut.expect( ut3_develop.ut_suite_tag_filter.apply(l_input_data, 'context_tag').count ).to_equal(3); + ut.expect( ut3_develop.ut_suite_tag_filter.apply(l_input_data, '!test_tag').count ).to_equal(0); + ut.expect( ut3_develop.ut_suite_tag_filter.apply(l_input_data, 'test_tag').count ).to_equal(3); + 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..e7b255d4b --- /dev/null +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pks @@ -0,0 +1,40 @@ +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; + + --%test(Conversion raises exception when empty brackets) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure conversion_throws_when_lb_rb; + + --%endcontext + + --%test( Test conversion of expression from tag into custom where filter for SQL) + procedure conv_from_tag_to_sql_filter; + + --%test( Regression for issue #1324 - corect filtering by tags with suitepath and contexts) + procedure filtering_is_correctly_applied_to_all_items; + +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..4dc10492b 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -375,8 +375,8 @@ end;'; procedure replace_multiline_comments is l_source clob; - l_actual clob; - l_expected clob; + l_actual dbms_preprocessor.source_lines_t; + l_expected dbms_preprocessor.source_lines_t; begin --Arrange l_source := q'[ @@ -390,11 +390,11 @@ create or replace package dummy as gv_text2 varchar2(200) := '/* multi-line comment in a multi-line string*/'; - -- ignored start of multi-line comment with multi-byte text � /* - -- ignored end of multi-line comment with multi-byte text � */ + -- ignored start of multi-line comment with multi-byte text ☺ /* + -- ignored end of multi-line comment with multi-byte text ☺ */ /* multi-line comment with - multi-byte characters ��� + multi-byte characters ☺☺☺ in it */ gv_text3 varchar2(200) := 'some text'; /* multiline comment*/ --followed by single-line comment /* multi-line comment in one line*/ @@ -403,7 +403,14 @@ create or replace package dummy as string*/}'; end; ]'; - l_expected := q'[ + + --Act + l_actual := ut3_develop.ut_utils.replace_multiline_comments( + ut3_tester_helper.main_helper.make_source( ut3_develop.ut_utils.clob_to_table(l_source) ) + ); + + --Assert + l_expected := ut3_tester_helper.main_helper.make_source( ut3_develop.ut_utils.clob_to_table( q'[ create or replace package dummy as -- single line comment with disabled /* multi-line comment */ @@ -414,8 +421,8 @@ create or replace package dummy as gv_text2 varchar2(200) := '/* multi-line comment in a multi-line string*/'; - -- ignored start of multi-line comment with multi-byte text � /* - -- ignored end of multi-line comment with multi-byte text � */ + -- ignored start of multi-line comment with multi-byte text ☺ /* + -- ignored end of multi-line comment with multi-byte text ☺ */ ]'||q'[ @@ -426,11 +433,9 @@ create or replace package dummy as in escaped q'multi-line string*/}'; end; -]'; +]' ) ); --Act - l_actual := ut3_develop.ut_utils.replace_multiline_comments(l_source); - --Assert - ut.expect(l_actual).to_equal(l_expected); + ut.expect(ut3_tester_helper.main_helper.lines_to_str(l_actual)).to_equal(ut3_tester_helper.main_helper.lines_to_str(l_expected)); end; procedure int_conv_ds_sec is @@ -488,6 +493,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/main_helper.pkb b/test/ut3_tester_helper/main_helper.pkb index f1dadec66..f60ce796c 100644 --- a/test/ut3_tester_helper/main_helper.pkb +++ b/test/ut3_tester_helper/main_helper.pkb @@ -155,6 +155,24 @@ create or replace package body main_helper is begin ut3_develop.ut_session_context.clear_all_context; end; + + function lines_to_str(a_lines dbms_preprocessor.source_lines_t) return varchar2 is + l_result varchar2(32767); + begin + for i in 1 .. a_lines.count loop + l_result := l_result || a_lines(i); + end loop; + return l_result; + end; + + function make_source(a_lines ut3_develop.ut_varchar2_list) return dbms_preprocessor.source_lines_t is + l_result dbms_preprocessor.source_lines_t; + begin + for i in 1 .. a_lines.count loop + l_result(i) := a_lines(i); + end loop; + return l_result; + end; end; / diff --git a/test/ut3_tester_helper/main_helper.pks b/test/ut3_tester_helper/main_helper.pks index 7decad88d..71575e145 100644 --- a/test/ut3_tester_helper/main_helper.pks +++ b/test/ut3_tester_helper/main_helper.pks @@ -47,5 +47,9 @@ create or replace package main_helper is procedure clear_ut_run_context; + function lines_to_str(a_lines dbms_preprocessor.source_lines_t) return varchar2; + + function make_source(a_lines ut3_develop.ut_varchar2_list) return dbms_preprocessor.source_lines_t; + end; / diff --git a/test/ut3_tester_helper/run_helper.pkb b/test/ut3_tester_helper/run_helper.pkb index 95f93f751..c56f31dc0 100644 --- a/test/ut3_tester_helper/run_helper.pkb +++ b/test/ut3_tester_helper/run_helper.pkb @@ -211,7 +211,7 @@ create or replace package body run_helper is begin execute immediate 'drop package ut3_user.test_db_link'; exception when others then null; end; end; - procedure create_suite_with_link is + procedure create_suite_with_link is pragma autonomous_transaction; begin create_db_link; @@ -250,7 +250,7 @@ create or replace package body run_helper is execute immediate 'grant execute on test_distributed_savepoint to public'; end; - procedure drop_suite_with_link is + procedure drop_suite_with_link is pragma autonomous_transaction; begin drop_db_link; @@ -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 @@ -605,18 +855,29 @@ create or replace package body run_helper is --%test procedure some_test; + end;]'; + execute immediate q'[ + create or replace package ut3_develop.package_that_is_not_a_test + as + --%parameter(a comment inside) + --%parameters( description ) + + --%test + procedure some_test; + end;]'; end; procedure drop_ut3_suite is pragma autonomous_transaction; begin + execute immediate q'[drop package ut3_develop.package_that_is_not_a_test]'; execute immediate q'[drop package ut3_develop.some_test_package]'; end; 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 @@ -665,5 +926,39 @@ create or replace package body run_helper is return l_result; end; + procedure slow_down_insert_into_table_buffer is + pragma autonomous_transaction; + begin + execute immediate q'[ + create or replace trigger ut3_develop.slow_down_buffer_insert + before insert on ut3_develop.ut_output_buffer_info_tmp + begin + dbms_session.sleep(1); + end;]'; + end; + + procedure cleanup_slow_down_insert_into_table_buffer is + pragma autonomous_transaction; + begin + execute immediate 'drop trigger ut3_develop.slow_down_buffer_insert'; + exception when others then + if sqlcode <> -4080 then raise; end if; + end; + + procedure run_as_job(a_job_action varchar2) is + l_job_name varchar2(50); + l_timestamp timestamp with time zone := current_timestamp; + pragma autonomous_transaction; + begin + l_job_name := 'utPLSQL__'||to_char(l_timestamp,'yyyymmddhh24missff'); + dbms_scheduler.create_job( + job_name => l_job_name, + job_type => 'PLSQL_BLOCK', + job_action => a_job_action, + start_date => l_timestamp, + enabled => TRUE, + auto_drop => TRUE + ); + end; end; / diff --git a/test/ut3_tester_helper/run_helper.pks b/test/ut3_tester_helper/run_helper.pks index ad0f615e5..6c5dd4a3b 100644 --- a/test/ut3_tester_helper/run_helper.pks +++ b/test/ut3_tester_helper/run_helper.pks @@ -74,5 +74,10 @@ create or replace package run_helper is a_name varchar2 := null ) return sys_refcursor; + procedure slow_down_insert_into_table_buffer; + + procedure cleanup_slow_down_insert_into_table_buffer; + + procedure run_as_job(a_job_action varchar2); end; / diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 41b969359..8c99af7d7 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -551,7 +551,7 @@ Failures:% ORA-04068: existing state of packages (DB_LOOPBACK%) has been discarded ORA-04061: existing state of package body "%.STATEFUL_PACKAGE" has been invalidated ORA-04065: not executed, altered or dropped package body "%.STATEFUL_PACKAGE"% - ORA-06512: at line 6% + ORA-06512: at line 7% 1 tests, 0 failed, 1 errored, 0 disabled, 0 warning(s)%'; --Act @@ -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; @@ -854,7 +854,7 @@ Failures:% l_results ut3_develop.ut_varchar2_list; begin select * bulk collect into l_random_results - from table ( ut3_develop.ut.run( 'ut3_tester_helper.test_package_1', a_random_test_order_seed => 6 ) ) + from table ( ut3_develop.ut.run( 'ut3_tester_helper.test_package_1', a_random_test_order_seed => 123 ) ) where trim(column_value) is not null and column_value not like 'Finished in %' and column_value not like '%Tests were executed with random order %'; @@ -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/api/test_ut_runner.pkb b/test/ut3_user/api/test_ut_runner.pkb index b6b7c67e7..d35495c70 100644 --- a/test/ut3_user/api/test_ut_runner.pkb +++ b/test/ut3_user/api/test_ut_runner.pkb @@ -386,14 +386,14 @@ end;'; select 'UT3_DEVELOP.UT_DEBUG_REPORTER', 'Y' from dual union all select 'UT3_DEVELOP.UT_COVERAGE_HTML_REPORTER', 'Y' from dual union all select 'UT3_DEVELOP.UT_COVERAGE_SONAR_REPORTER', 'Y' from dual union all - select 'UT3_DEVELOP.UT_COVERALLS_REPORTER', 'Y' from dual union all select 'UT3_DEVELOP.UT_DOCUMENTATION_REPORTER', 'Y' from dual union all select 'UT3_DEVELOP.UT_JUNIT_REPORTER', 'Y' from dual union all select 'UT3_DEVELOP.UT_REALTIME_REPORTER', 'Y' from dual union all select 'UT3_DEVELOP.UT_SONAR_TEST_REPORTER', 'Y' from dual union all select 'UT3_DEVELOP.UT_TEAMCITY_REPORTER', 'Y' from dual union all select 'UT3_DEVELOP.UT_TFS_JUNIT_REPORTER', 'Y' from dual union all - select 'UT3_DEVELOP.UT_XUNIT_REPORTER', 'Y' from dual + select 'UT3_DEVELOP.UT_XUNIT_REPORTER', 'Y' from dual union all + select 'UT3_DEVELOP.UT_TAP_REPORTER', 'Y' from dual order by 1; --Act open l_actual for select * from table(ut3_develop.ut_runner.GET_REPORTERS_LIST()) order by 1; diff --git a/test/ut3_user/expectations/binary/test_to_be_within.pkb b/test/ut3_user/expectations/binary/test_to_be_within.pkb index 032b684a5..c9aae6ee2 100644 --- a/test/ut3_user/expectations/binary/test_to_be_within.pkb +++ b/test/ut3_user/expectations/binary/test_to_be_within.pkb @@ -199,7 +199,7 @@ create or replace package body test_to_be_within is begin --Arrange --Act - ut3_develop.ut.expect(sysdate).to_be_within(INTERVAL '1' MONTH).of_(sysdate+ 46); + ut3_develop.ut.expect(sysdate).to_be_within(INTERVAL '1' MONTH).of_(sysdate+ 47); --Assert l_expected_message := q'[Actual: % (date) was expected to be within 1 month of % (date)]'; l_actual_message := ut3_tester_helper.main_helper.get_failed_expectations(1); 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_coveralls_reporter.pkb b/test/ut3_user/reporters/test_coverage/test_coveralls_reporter.pkb deleted file mode 100644 index e3806cd97..000000000 --- a/test/ut3_user/reporters/test_coverage/test_coveralls_reporter.pkb +++ /dev/null @@ -1,77 +0,0 @@ -create or replace package body test_coveralls_reporter is - - procedure report_on_file is - l_expected clob; - l_actual clob; - l_file_path varchar2(250); - begin - --Arrange - l_file_path := 'test/ut3_develop.'||ut3_tester_helper.coverage_helper.covered_package_name||'.pkb'; - l_expected := q'[{"source_files":[ -{ "name": "]'||l_file_path||q'[", -"coverage": [ -null, -null, -null, -3, -null, -0 -] -} -]} - ]'; - l_actual := - ut3_tester_helper.coverage_helper.run_tests_as_job( - q'[ - ut3_develop.ut.run( - a_path => 'ut3_develop.test_dummy_coverage', - a_reporter => ut3_develop.ut_coveralls_reporter( ), - a_source_files => ut3_develop.ut_varchar2_list( ']'||l_file_path||q'[' ), - a_test_files => ut3_develop.ut_varchar2_list( ) - ) - ]' - ); - --Assert - ut.expect(l_actual).to_equal(l_expected); - end; - - procedure report_zero_coverage is - l_expected clob; - l_actual clob; - begin - --Arrange - l_expected := q'[{"source_files":[ -{ "name": "package body ut3_develop.]'||ut3_tester_helper.coverage_helper.covered_package_name||q'[", -"coverage": [ -0, -0, -0, -0, -0, -0, -0, -0, -0 -] -} -]} - ]'; - - --Act - l_actual := - ut3_tester_helper.coverage_helper.run_tests_as_job( - q'[ - ut3_develop.ut.run( - 'ut3_develop.test_dummy_coverage.zero_coverage', - ut3_develop.ut_coveralls_reporter(), - a_include_objects => ut3_develop.ut_varchar2_list('UT3_DEVELOP.]'||ut3_tester_helper.coverage_helper.covered_package_name||q'[') - ) - ]' - ); - --Assert - ut.expect(l_actual).to_equal(l_expected); - - end; - -end; -/ diff --git a/test/ut3_user/reporters/test_coverage/test_coveralls_reporter.pks b/test/ut3_user/reporters/test_coverage/test_coveralls_reporter.pks deleted file mode 100644 index d958b8f12..000000000 --- a/test/ut3_user/reporters/test_coverage/test_coveralls_reporter.pks +++ /dev/null @@ -1,13 +0,0 @@ -create or replace package test_coveralls_reporter is - - --%suite(ut_coveralls_reporter) - --%suitepath(utplsql.test_user.reporters.test_coverage) - - --%test(reports on a project file mapped to database object) - procedure report_on_file; - - --%test(reports zero coverage on each line of non-executed database object) - procedure report_zero_coverage; - -end; -/ 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..4b06f725f 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 @@ -206,5 +207,26 @@ create or replace package body test_proftab_coverage is ut.expect(l_actual).to_be_like(coverage_helper.substitute_covered_package(l_expected)); end; + procedure no_issue_with_duplicate_source is + l_actual clob; + begin + --Act + l_actual := + ut3_tester_helper.coverage_helper.run_tests_as_job( + coverage_helper.substitute_covered_package( + q'[ + ut3_develop.ut.run( + 'ut3_develop.test_dummy_coverage.zero_coverage', + ut3_develop.ut_coverage_sonar_reporter(), + a_include_schema_expr =>'SYS' + ) + ]' + ) + ); + + --Running above line is successful if there is no exception thrown due to duplicate lines in coverage + ut.expect(l_actual).not_to_be_null(); + end; + end; / diff --git a/test/ut3_user/reporters/test_coverage/test_proftab_coverage.pks b/test/ut3_user/reporters/test_coverage/test_proftab_coverage.pks index 740862f86..fb1d0cfe3 100644 --- a/test/ut3_user/reporters/test_coverage/test_proftab_coverage.pks +++ b/test/ut3_user/reporters/test_coverage/test_proftab_coverage.pks @@ -28,5 +28,8 @@ create or replace package test_proftab_coverage is --%test(reports zero coverage on each line of non-executed database object - Issue #917) procedure report_zero_coverage; + --%test(Allows reporting coverage on objects across SYS or MDSYS without errors - Issue #1323 ) + procedure no_issue_with_duplicate_source; + end; / diff --git a/test/ut3_user/reporters/test_documentation_reporter.pkb b/test/ut3_user/reporters/test_documentation_reporter.pkb index 6ace70c49..d9c5d48c8 100644 --- a/test/ut3_user/reporters/test_documentation_reporter.pkb +++ b/test/ut3_user/reporters/test_documentation_reporter.pkb @@ -34,12 +34,11 @@ 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 + ORA-06512: at line 7 Finished in % seconds 5 tests, 1 failed, 1 errored, 2 disabled, 0 warning(s)%]'; diff --git a/test/ut3_user/reporters/test_realtime_reporter.pkb b/test/ut3_user/reporters/test_realtime_reporter.pkb index 2dafa71d0..d4d83f7a3 100644 --- a/test/ut3_user/reporters/test_realtime_reporter.pkb +++ b/test/ut3_user/reporters/test_realtime_reporter.pkb @@ -408,9 +408,9 @@ 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, ''); + ut3_tester_helper.main_helper.append_to_list(l_expected_list, '%ORA-06512: at line 7]]>'); l_expected := ut3_tester_helper.main_helper.table_to_clob(l_expected_list); ut.expect(l_actual).to_be_like(l_expected); end error_stack_of_test; @@ -427,7 +427,7 @@ create or replace package body test_realtime_reporter as = 'realtime_reporting.check_realtime_reporting3'; ut3_tester_helper.main_helper.append_to_list(l_expected_list, ''); + ut3_tester_helper.main_helper.append_to_list(l_expected_list, '%ORA-06512: at line 7]]>'); l_expected := ut3_tester_helper.main_helper.table_to_clob(l_expected_list); ut.expect(l_actual).to_be_like(l_expected); end error_stack_of_testsuite; diff --git a/test/ut3_user/reporters/test_tap_reporter.pkb b/test/ut3_user/reporters/test_tap_reporter.pkb new file mode 100644 index 000000000..786c53ee0 --- /dev/null +++ b/test/ut3_user/reporters/test_tap_reporter.pkb @@ -0,0 +1,254 @@ +create or replace package body test_tap_reporter as + + gc_boilerplate_suitepath_expression constant varchar2(300) := 'TAP version 14\s*1..1\s*# Subtest: org\s{5}1..1\s{5}# Subtest: utplsql\s{9}1..1\s{9}# Subtest: tests\s{13}1..1\s{13}# Subtest: helpers\s{17}1..1\s{17}# Subtest: A suite for testing different outcomes from reporters'; + + + procedure compile_tests as + pragma autonomous_transaction; + begin + + execute immediate q'[ + create or replace package test_tap_escaping as + --%suite(Some \ and # to be escaped) + + --%test(Even more \\ and multiple ###) + procedure more_escaping; + + --%test(Disabled test) + --%disabled(With \ and # in skip reason) + procedure not_skipping_escapes; + + --%test(Escaped Comments) + procedure escaped_comments; + + --%context(Some context) + + --%test(Another disabled test) + --%disabled + procedure another_disabled_test; + + --%endcontext + end test_tap_escaping; + ]'; + + execute immediate q'[ + create or replace package body test_tap_escaping as + + procedure more_escaping as + begin + ut.expect(1).to_equal(1); + end more_escaping; + + + procedure not_skipping_escapes as + begin + ut.expect(10).to_equal(1); + end not_skipping_escapes; + + procedure escaped_comments as + begin + dbms_output.put_line('This \ and # should not be escaped, and this not as well!!!'); + ut.expect(1).to_equal(1); + end escaped_comments; + + procedure another_disabled_test as + begin + ut.expect(10).to_equal(1); + end; + end test_tap_escaping; + ]'; + + end compile_tests; + + + procedure simple_succeeding_test as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := gc_boilerplate_suitepath_expression || '\s{21}1..1\s{21}# \s{21}# Subtest: A description of some context\s{25}1..1\s{25}ok - passing_test\s{25}# \s{25}# \s{25}# \s{25}# \s{25}# \s{21}# \sok - org\s*'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_reporters.passing_test',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_match(l_expected); + end simple_succeeding_test; + + procedure simple_failing_test as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := gc_boilerplate_suitepath_expression || q'[\s{21}1..1\s{21}# \s{21}not ok - a test with failing assertion\s{23}---\s{23}message: '"Fails as values are different"'\s{23}severity: fail\s{23}...\s{21}# \s{21}# \s{21}# \s{21}# \snot ok - org\s*]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_reporters.failing_test',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_match(l_expected); + end simple_failing_test; + + + procedure simple_erroring_test as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := gc_boilerplate_suitepath_expression || q'[\s{21}1..1\s{21}# \s{21}not ok - a test raising unhandled exception\s{23}---\s{23}message: |\s{25ORA-06502: .*\s{25}ORA-06512: at line [[:digit:]]+\s{23}severity: error\s{23}...\s{21}# \s{21}# \s{21}# \s{21}# \snot ok - org\s*]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_reporters.erroring_test',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_match(l_expected); + end simple_erroring_test; + + + procedure disabled_test as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := gc_boilerplate_suitepath_expression || q'[\s{21}1..1\s{21}# \s{21}ok - a disabled test # SKIP: Disabled for testing purpose\s{21}# \sok - org\s*]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_reporters.disabled_test',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_match(l_expected); + end disabled_test; + + + procedure disabled_test_no_description as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := gc_boilerplate_suitepath_expression || q'[\s{21}1..1\s{21}# \s{21}ok - a disabled test with no reason # SKIP\s{21}# \sok - org\s*]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_reporters.disabled_test_no_reason',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_match(l_expected); + end disabled_test_no_description; + + + procedure multiple_tests_different_outcome as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'[TAP version 14\s*1..1\s*# Subtest: org.*# Subtest: A suite.*\s{21}1..5\s{21}# \s{21}# Subtest: A desc.*\s{25}1..1\s{25}ok - passing_test\s{25}# \s{25}# \s{21}not ok - a test w.*\s{23}---\s{23}message:.*\s{21}# .*\s{21}# not ok - a test rai.*\s{23}---\s{23}message: |.*ok - a disabled test # SKIP: Disabled for testing purpose.*ok - a dis.* # SKIP\s{21}# \snot ok - org\s*]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_reporters',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_match(l_expected, 'n'); + end multiple_tests_different_outcome; + + + procedure escape_suite_name as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'[%# Subtest: Some \\ and \# to be escaped%ok - Some \\ and \# to be escaped%]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_tap_escaping.more_escaping',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); + end escape_suite_name; + + + procedure escape_multiple_characters_test_name as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'[%ok - Even more \\\\ and multiple \#\#\#%]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_tap_escaping.more_escaping',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); + end escape_multiple_characters_test_name; + + + procedure special_characters_in_disabled_reason as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'[%ok - Disabled test # SKIP: With \\ and \# in skip reason%]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_tap_escaping.not_skipping_escapes',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); + end special_characters_in_disabled_reason; + + + procedure special_characters_in_comment as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'[%ok - Escaped Comments%# This \ and # should not be escaped, and this not as well!!!%]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_tap_escaping.escaped_comments',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); + end special_characters_in_comment; + + + procedure context_as_commented_subtests as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'[%# Subtest: A suite for testing different outcomes from reporters%# Subtest: A description of some context%]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_reporters.passing_test',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); + + end context_as_commented_subtests; + + + procedure suitepath_as_chopped_subtests as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'[.*# Subtest: org.*1\s{5}# Subtest: utplsql.*1\s{9}# Subtest: tests.*1\s{13}# Subtest: helpers.*1\s{17}# Subtest: A suite for testing different outcomes from reporters.*]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_reporters.passing_test',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_match(l_expected, 'n'); + + end suitepath_as_chopped_subtests; + + + procedure include_context_with_skipped_tests as + l_output_data ut3_develop.ut_varchar2_list; + l_expected varchar2(32767); + begin + l_expected := q'[%# Subtest: Some context%]'; + + select * + bulk collect into l_output_data + from table(ut3_develop.ut.run('test_tap_escaping.another_disabled_test',ut3_develop.ut_tap_reporter())); + + ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_output_data)).to_be_like(l_expected); + + end include_context_with_skipped_tests; + + + procedure drop_help_tests as + pragma autonomous_transaction; + begin + execute immediate 'drop package test_tap_escaping'; + end drop_help_tests; +end test_tap_reporter; +/ \ No newline at end of file diff --git a/test/ut3_user/reporters/test_tap_reporter.pks b/test/ut3_user/reporters/test_tap_reporter.pks new file mode 100644 index 000000000..27f9651bc --- /dev/null +++ b/test/ut3_user/reporters/test_tap_reporter.pks @@ -0,0 +1,53 @@ +create or replace package test_tap_reporter as + + --%suite(ut_tap_reporter) + --%suitepath(utplsql.test_user.reporters) + + --%beforeall + procedure compile_tests; + + --%test(Simple succeeding test) + procedure simple_succeeding_test; + + --%test(Simple failing test) + procedure simple_failing_test; + + --%test(Simple erroring test) + procedure simple_erroring_test; + + --%test(Skipped test) + procedure disabled_test; + + --%test(Skipped test without description) + procedure disabled_test_no_description; + + --%test(Multiple tests with different outcome) + procedure multiple_tests_different_outcome; + + --%test(Escape special characters in suite name) + procedure escape_suite_name; + + --%test(Escape multiple special characters in test name) + procedure escape_multiple_characters_test_name; + + --%test(Disabled Test with special characters in disable reason) + procedure special_characters_in_disabled_reason; + + --%test(Don't escape special characters in comment) + procedure special_characters_in_comment; + + --%test(Include context as commented subtests) + procedure context_as_commented_subtests; + + --%test(Suitepath as chopped subtests) + procedure suitepath_as_chopped_subtests; + + --%test(Include context with only skipped tests in output) + procedure include_context_with_skipped_tests; + + + --%afterall + procedure drop_help_tests; + +end test_tap_reporter; +/ \ No newline at end of file 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..105517763 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(Production_context_Features) + + --%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;