From f5fa46800c1bfe5f5db56a2b6ca759b97e49e7c0 Mon Sep 17 00:00:00 2001 From: Jacek Gebal Date: Tue, 2 Jul 2019 01:43:30 +0100 Subject: [PATCH 1/7] [WIP] Ability to run expectations without test > TODO - documentation updates Added ability to run expectations without tests. Expectation, when executed without test is reporter straight to dbms_output. So it is now possible to execute: `exec ut.expect(1).to_equal(0);` And get result: ``` FAILURE Actual: 1 (number) was expected to equal: 0 (number) ``` Resolves #956 Additionally: - utPLSQL is now cleaning up the application_info after run. - utPLSQL is setting session context during run, making it possible to access some of utPLSQL info within the test procedures directly. Resolves #781 The information is provided in sys_context(`UT3_INFO`,...). Following attributes are getting populated: - Always: - RUN_PATHS - SUITE_DESCRIPTION - SUITE_PACKAGE - SUITE_PATH - SUITE_START_TIME - CURRENT_EXECUTABLE_NAME - CURRENT_EXECUTABLE_TYPE - When running in suite context - CONTEXT_NAME - CONTEXT_PATH - CONTEXT_START_TIME - After first executable in suite - TIME_IN_SUITE - After first executable in suite context - TIME_IN_CONTEXT - When running a test or before/after each/test - TEST_DESCRIPTION - TEST_NAME - TEST_START_TIME - After first executable in test - TIME_IN_TEST --- .travis/install_utplsql_release.sh | 2 + source/api/ut_runner.pkb | 1 + .../session_context/ut_session_context.pkb | 52 +++ .../session_context/ut_session_context.pks | 45 ++ .../core/session_context/ut_session_info.tpb | 199 +++++++++ .../core/session_context/ut_session_info.tps | 52 +++ source/core/types/ut_executable.tpb | 4 - source/core/types/ut_executable_test.tpb | 17 + source/core/types/ut_reporter_info.tps | 2 +- source/core/types/ut_suite.tpb | 6 +- source/core/types/ut_test.tpb | 2 +- source/core/ut_expectation_processor.pkb | 17 +- source/core/ut_suite_cache_manager.pkb | 4 +- source/core/ut_utils.pkb | 10 - source/core/ut_utils.pks | 10 - source/install.sql | 15 +- source/uninstall_objects.sql | 4 + test/install_ut3_user_tests.sql | 2 + test/ut3_tester_helper/main_helper.pkb | 12 +- test/ut3_tester_helper/main_helper.pks | 4 + test/ut3_user/api/test_ut_run.pkb | 389 +++++++++++++++++- test/ut3_user/api/test_ut_run.pks | 51 ++- test/ut3_user/expectations.pkb | 31 ++ test/ut3_user/expectations.pks | 14 + 24 files changed, 899 insertions(+), 46 deletions(-) create mode 100644 source/core/session_context/ut_session_context.pkb create mode 100644 source/core/session_context/ut_session_context.pks create mode 100644 source/core/session_context/ut_session_info.tpb create mode 100644 source/core/session_context/ut_session_info.tps create mode 100644 test/ut3_user/expectations.pkb create mode 100644 test/ut3_user/expectations.pks diff --git a/.travis/install_utplsql_release.sh b/.travis/install_utplsql_release.sh index 6dc56c3f3..3e608b08f 100755 --- a/.travis/install_utplsql_release.sh +++ b/.travis/install_utplsql_release.sh @@ -43,6 +43,8 @@ fi "$SQLCLI" sys/$ORACLE_PWD@//$CONNECTION_STR AS SYSDBA < gc_context_name, attribute => a_name ); + end; + + procedure clear_all_context is + begin + dbms_session.clear_all_context( namespace => gc_context_name ); + end; + + function is_ut_run return boolean is + l_paths varchar2(32767); + begin + l_paths := sys_context(gc_context_name, 'RUN_PATHS'); + return l_paths is not null; + end; + + function get_namespace return varchar2 is + begin + return gc_context_name; + end; + +end; +/ \ No newline at end of file diff --git a/source/core/session_context/ut_session_context.pks b/source/core/session_context/ut_session_context.pks new file mode 100644 index 000000000..bbe256db4 --- /dev/null +++ b/source/core/session_context/ut_session_context.pks @@ -0,0 +1,45 @@ +create or replace package ut_session_context as + /* + utPLSQL - Version 3 + Copyright 2016 - 2019 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. + */ + + /* + * Sets value of a context + */ + procedure set_context(a_name varchar2, a_value varchar2); + + /* + * Clears value of a context + */ + procedure clear_context(a_name varchar2); + + /* + * Clears entire context for utPLSQL run + */ + procedure clear_all_context; + + /* + * Returns true, if session context UT3_INFO is not empty + */ + function is_ut_run return boolean; + + /* + * Returns utPLSQL session context namespace name + */ + function get_namespace return varchar2; + +end; +/ \ No newline at end of file diff --git a/source/core/session_context/ut_session_info.tpb b/source/core/session_context/ut_session_info.tpb new file mode 100644 index 000000000..398ff0067 --- /dev/null +++ b/source/core/session_context/ut_session_info.tpb @@ -0,0 +1,199 @@ +create or replace type body ut_session_info as + /* + utPLSQL - Version 3 + Copyright 2016 - 2019 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_session_info(self in out nocopy ut_session_info) return self as result is + begin + self.self_type := $$plsql_unit; + dbms_application_info.read_client_info( client_info ); + dbms_application_info.read_module( module, action ); + return; + end; + + -- run hooks + member procedure before_calling_run(self in out nocopy ut_session_info, a_run in ut_run) is + begin + ut_session_context.set_context( 'run_paths', ut_utils.to_string( ut_utils.table_to_clob( a_run.run_paths,',' ), null ) ); + dbms_application_info.set_module( 'utPLSQL', null ); + end; + + member procedure after_calling_run(self in out nocopy ut_session_info, a_run in ut_run) is + begin + ut_session_context.clear_context( 'run_paths' ); + dbms_application_info.set_module( module, action ); + dbms_application_info.set_client_info( client_info ); + end; + + -- suite hooks + member procedure before_calling_suite(self in out nocopy ut_session_info, a_suite in ut_logical_suite) is + begin + if a_suite is not of (ut_suite_context) then + suite_start_time := a_suite.start_time; + ut_session_context.set_context( 'suite_path', a_suite.path ); + ut_session_context.set_context( 'suite_package', a_suite.object_owner||'.'||a_suite.object_name ); + ut_session_context.set_context( 'suite_description', a_suite.description ); + ut_session_context.set_context( 'suite_start_time', ut_utils.to_string(suite_start_time) ); + dbms_application_info.set_module( 'utPLSQL', a_suite.object_name ); + else + context_start_time := a_suite.start_time; + ut_session_context.set_context( 'context_name', a_suite.name ); + ut_session_context.set_context( 'context_path', a_suite.path); + ut_session_context.set_context( 'context_description', a_suite.description ); + ut_session_context.set_context( 'context_start_time', ut_utils.to_string(context_start_time) ); + end if; + end; + + member procedure after_calling_suite(self in out nocopy ut_session_info, a_suite in ut_logical_suite) is + begin + if a_suite is not of (ut_suite_context) then + ut_session_context.clear_context( 'suite_package' ); + ut_session_context.clear_context( 'suite_path' ); + ut_session_context.clear_context( 'suite_description' ); + ut_session_context.clear_context( 'suite_start_time' ); + ut_session_context.clear_context( 'time_in_suite' ); + suite_start_time := null; + else + ut_session_context.clear_context( 'context_name' ); + ut_session_context.clear_context( 'context_path' ); + ut_session_context.clear_context( 'context_description' ); + ut_session_context.clear_context( 'context_start_time' ); + ut_session_context.clear_context( 'time_in_context' ); + context_start_time := null; + end if; + end; + + + member procedure before_calling_test(self in out nocopy ut_session_info, a_test in ut_test) is + begin + test_start_time := a_test.start_time; + ut_session_context.set_context( 'test_name', a_test.object_owner||'.'||a_test.object_name||'.'||a_test.name ); + ut_session_context.set_context( 'test_description', a_test.description ); + ut_session_context.set_context( 'test_start_time', ut_utils.to_string(test_start_time) ); + end; + + member procedure after_calling_test (self in out nocopy ut_session_info, a_test in ut_test) is + begin + ut_session_context.clear_context( 'test_name' ); + ut_session_context.clear_context( 'test_description' ); + ut_session_context.clear_context( 'test_start_time' ); + ut_session_context.clear_context( 'time_in_test' ); + test_start_time := null; + end; + + member procedure before_calling_executable(self in out nocopy ut_session_info, a_executable in ut_executable) is + begin + ut_session_context.set_context( 'current_executable_type', a_executable.executable_type ); + ut_session_context.set_context( + 'current_executable_name', + a_executable.owner_name||'.'||a_executable.object_name||'.'||a_executable.procedure_name + ); + dbms_application_info.set_client_info( a_executable.procedure_name ); + if suite_start_time is not null then + ut_session_context.set_context( 'time_in_suite', current_timestamp - suite_start_time ); + if context_start_time is not null then + ut_session_context.set_context( 'time_in_context', current_timestamp - context_start_time ); + end if; + if test_start_time is not null then + ut_session_context.set_context( 'time_in_test', current_timestamp - test_start_time ); + end if; + end if; + end; + + member procedure after_calling_executable(self in out nocopy ut_session_info, a_executable in ut_executable) is + begin + ut_session_context.clear_context( 'current_executable_type' ); + ut_session_context.clear_context( 'current_executable_name' ); + dbms_application_info.set_client_info( null ); + end; + + member procedure on_finalize(self in out nocopy ut_session_info, a_run in ut_run) is + begin + dbms_application_info.set_client_info( client_info ); + dbms_application_info.set_module( module, action ); + ut_session_context.clear_all_context(); + end; + + overriding member function get_supported_events return ut_varchar2_list is + begin + return ut_varchar2_list( + ut_event_manager.gc_before_run, + ut_event_manager.gc_before_suite, + ut_event_manager.gc_before_test, + ut_event_manager.gc_before_before_all, + ut_event_manager.gc_before_before_each, + ut_event_manager.gc_before_before_test, + ut_event_manager.gc_before_test_execute, + ut_event_manager.gc_before_after_test, + ut_event_manager.gc_before_after_each, + ut_event_manager.gc_before_after_all, + ut_event_manager.gc_after_run, + ut_event_manager.gc_after_suite, + ut_event_manager.gc_after_test, + ut_event_manager.gc_after_before_all, + ut_event_manager.gc_after_before_each, + ut_event_manager.gc_after_before_test, + ut_event_manager.gc_after_test_execute, + ut_event_manager.gc_after_after_test, + ut_event_manager.gc_after_after_each, + ut_event_manager.gc_after_after_all, + ut_event_manager.gc_finalize + ); + end; + + overriding member procedure on_event( self in out nocopy ut_session_info, a_event_name varchar2, a_event_item ut_event_item) is + begin + case + when a_event_name in ( + ut_event_manager.gc_before_before_all, + ut_event_manager.gc_before_before_each, + ut_event_manager.gc_before_before_test, + ut_event_manager.gc_before_test_execute, + ut_event_manager.gc_before_after_test, + ut_event_manager.gc_before_after_each, + ut_event_manager.gc_before_after_all + ) + then before_calling_executable(treat(a_event_item as ut_executable)); + when a_event_name in ( + ut_event_manager.gc_after_before_all, + ut_event_manager.gc_after_before_each, + ut_event_manager.gc_after_before_test, + ut_event_manager.gc_after_test_execute, + ut_event_manager.gc_after_after_test, + ut_event_manager.gc_after_after_each, + ut_event_manager.gc_after_after_all + ) + then after_calling_executable(treat(a_event_item as ut_executable)); + when a_event_name = ut_event_manager.gc_before_test + then self.before_calling_test(treat(a_event_item as ut_test)); + when a_event_name = ut_event_manager.gc_after_test + then self.after_calling_test(treat(a_event_item as ut_test)); + when a_event_name = ut_event_manager.gc_after_suite + then after_calling_suite(treat(a_event_item as ut_logical_suite)); + when a_event_name = ut_event_manager.gc_before_suite + then before_calling_suite(treat(a_event_item as ut_logical_suite)); + when a_event_name = ut_event_manager.gc_before_run + then before_calling_run(treat(a_event_item as ut_run)); + when a_event_name = ut_event_manager.gc_after_run + then after_calling_run(treat(a_event_item as ut_run)); + when a_event_name = ut_event_manager.gc_finalize + then on_finalize(treat(a_event_item as ut_run)); + else null; + end case; + end; + +end; +/ \ No newline at end of file diff --git a/source/core/session_context/ut_session_info.tps b/source/core/session_context/ut_session_info.tps new file mode 100644 index 000000000..451d7b8f6 --- /dev/null +++ b/source/core/session_context/ut_session_info.tps @@ -0,0 +1,52 @@ +create or replace type ut_session_info under ut_event_listener ( + /* + utPLSQL - Version 3 + Copyright 2016 - 2019 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. + */ + + module varchar2(4000), + action varchar2(4000), + client_info varchar2(4000), + suite_start_time timestamp, + context_start_time timestamp, + test_start_time timestamp, + constructor function ut_session_info(self in out nocopy ut_session_info) return self as result, + + member procedure before_calling_run(self in out nocopy ut_session_info, a_run in ut_run), + member procedure after_calling_run (self in out nocopy ut_session_info, a_run in ut_run), + + member procedure before_calling_suite(self in out nocopy ut_session_info, a_suite in ut_logical_suite), + member procedure after_calling_suite(self in out nocopy ut_session_info, a_suite in ut_logical_suite), + + member procedure before_calling_executable(self in out nocopy ut_session_info, a_executable in ut_executable), + member procedure after_calling_executable (self in out nocopy ut_session_info, a_executable in ut_executable), + + member procedure before_calling_test(self in out nocopy ut_session_info, a_test in ut_test), + member procedure after_calling_test (self in out nocopy ut_session_info, a_test in ut_test), + + member procedure on_finalize(self in out nocopy ut_session_info, a_run in ut_run), + + /** + * Returns the list of events that are supported by particular implementation of the reporter + */ + overriding member function get_supported_events return ut_varchar2_list, + + /** + * Delegates execution of event into individual reporting procedures + */ + overriding member procedure on_event( self in out nocopy ut_session_info, a_event_name varchar2, a_event_item ut_event_item) + +) final +/ \ No newline at end of file diff --git a/source/core/types/ut_executable.tpb b/source/core/types/ut_executable.tpb index 060f416aa..826a5aaa1 100644 --- a/source/core/types/ut_executable.tpb +++ b/source/core/types/ut_executable.tpb @@ -104,9 +104,6 @@ create or replace type body ut_executable is begin l_start_transaction_id := dbms_transaction.local_transaction_id(true); - -- report to application_info - ut_utils.set_client_info(self.procedure_name); - --listener - before call to executable ut_event_manager.trigger_event('before_'||self.executable_type, self); @@ -173,7 +170,6 @@ create or replace type body ut_executable is if l_start_transaction_id != l_end_transaction_id or l_end_transaction_id is null then a_item.add_transaction_invalidator(self.form_name()); end if; - ut_utils.set_client_info(null); return l_completed_without_errors; diff --git a/source/core/types/ut_executable_test.tpb b/source/core/types/ut_executable_test.tpb index 09ffab55d..a11797a54 100644 --- a/source/core/types/ut_executable_test.tpb +++ b/source/core/types/ut_executable_test.tpb @@ -1,4 +1,21 @@ create or replace type body ut_executable_test as + /* + utPLSQL - Version 3 + Copyright 2016 - 2019 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_executable_test( self in out nocopy ut_executable_test, a_owner varchar2, a_package varchar2, a_procedure_name varchar2, a_executable_type varchar2 diff --git a/source/core/types/ut_reporter_info.tps b/source/core/types/ut_reporter_info.tps index a4028974f..d1f159211 100644 --- a/source/core/types/ut_reporter_info.tps +++ b/source/core/types/ut_reporter_info.tps @@ -15,7 +15,7 @@ create or replace type ut_reporter_info as object ( See the License for the specific language governing permissions and limitations under the License. */ - object_name varchar2(250), + object_name varchar2(250), is_output_reporter varchar2(1), is_instantiable varchar2(1), is_final varchar2(1) diff --git a/source/core/types/ut_suite.tpb b/source/core/types/ut_suite.tpb index ccf004a4b..baaaf044d 100644 --- a/source/core/types/ut_suite.tpb +++ b/source/core/types/ut_suite.tpb @@ -43,13 +43,11 @@ create or replace type body ut_suite as begin ut_utils.debug_log('ut_suite.execute'); - ut_utils.set_action(self.object_name); - if self.get_disabled_flag() then self.mark_as_skipped(); else - ut_event_manager.trigger_event(ut_event_manager.gc_before_suite, self); self.start_time := current_timestamp; + ut_event_manager.trigger_event(ut_event_manager.gc_before_suite, self); l_suite_savepoint := self.create_savepoint_if_needed(); @@ -83,8 +81,6 @@ create or replace type body ut_suite as ut_event_manager.trigger_event(ut_event_manager.gc_after_suite, self); end if; - ut_utils.set_action(null); - return l_no_errors; end; diff --git a/source/core/types/ut_test.tpb b/source/core/types/ut_test.tpb index 7b2c495de..9fbcf9269 100644 --- a/source/core/types/ut_test.tpb +++ b/source/core/types/ut_test.tpb @@ -56,8 +56,8 @@ create or replace type body ut_test as if self.get_disabled_flag() then mark_as_skipped(); else - ut_event_manager.trigger_event(ut_event_manager.gc_before_test, self); self.start_time := current_timestamp; + ut_event_manager.trigger_event(ut_event_manager.gc_before_test, self); l_savepoint := self.create_savepoint_if_needed(); diff --git a/source/core/ut_expectation_processor.pkb b/source/core/ut_expectation_processor.pkb index 96ab022e4..4d773f833 100644 --- a/source/core/ut_expectation_processor.pkb +++ b/source/core/ut_expectation_processor.pkb @@ -78,10 +78,19 @@ create or replace package body ut_expectation_processor as end get_failed_expectations; procedure add_expectation_result(a_expectation_result ut_expectation_result) is - begin - ut_event_manager.trigger_event(ut_event_manager.gc_debug, a_expectation_result); - g_expectations_called.extend; - g_expectations_called(g_expectations_called.last) := a_expectation_result; + l_results ut_varchar2_list; + begin + if ut_session_context.is_ut_run then + ut_event_manager.trigger_event(ut_event_manager.gc_debug, a_expectation_result); + g_expectations_called.extend; + g_expectations_called(g_expectations_called.last) := a_expectation_result; + else + l_results := a_expectation_result.get_result_lines(); + dbms_output.put_line( upper( ut_utils.test_result_to_char( a_expectation_result.status ) ) || ''); + for i in 1 .. l_results.count loop + dbms_output.put_line( ' ' || l_results(i) ); + end loop; + end if; end; procedure report_failure(a_message in varchar2) is diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index f5c472e29..a5ffd0e0d 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -411,7 +411,7 @@ create or replace package body ut_suite_cache_manager is select count( 1 ) into l_count from dual where exists( select 1 - from ut_suite_cache_package c + from ut_suite_cache c where c.object_owner = a_owner_name and c.object_name = a_package_name ); @@ -419,7 +419,7 @@ create or replace package body ut_suite_cache_manager is select count( 1 ) into l_count from dual where exists( select 1 - from ut_suite_cache_package c + from ut_suite_cache c where c.object_owner = a_owner_name ); end if; diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index f8c411ec1..95378a18e 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -446,16 +446,6 @@ create or replace package body ut_utils is return l_result; end; - procedure set_action(a_text in varchar2) is - begin - dbms_application_info.set_module('utPLSQL', a_text); - end; - - procedure set_client_info(a_text in varchar2) is - begin - dbms_application_info.set_client_info(a_text); - end; - function to_xpath(a_list varchar2, a_ancestors varchar2 := '/*/') return varchar2 is l_xpath varchar2(32767) := a_list; begin diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index 779f941c7..d5b8c21ac 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -295,16 +295,6 @@ create or replace package ut_utils authid definer is function convert_collection(a_collection ut_varchar2_list) return ut_varchar2_rows; - /** - * Set session's action and module using dbms_application_info - */ - procedure set_action(a_text in varchar2); - - /** - * Set session's client info using dbms_application_info - */ - procedure set_client_info(a_text in varchar2); - function to_xpath(a_list varchar2, a_ancestors varchar2 := '/*/') return varchar2; function to_xpath(a_list ut_varchar2_list, a_ancestors varchar2 := '/*/') return varchar2; diff --git a/source/install.sql b/source/install.sql index 0e28fa3ab..1f9913b0a 100644 --- a/source/install.sql +++ b/source/install.sql @@ -31,9 +31,17 @@ prompt &&line_separator alter session set current_schema = &&ut3_owner; @@check_object_grants.sql -@@check_sys_grants.sql "'CREATE TYPE','CREATE VIEW','CREATE SYNONYM','CREATE SEQUENCE','CREATE PROCEDURE','CREATE TABLE'" +@@check_sys_grants.sql "'CREATE TYPE','CREATE VIEW','CREATE SYNONYM','CREATE SEQUENCE','CREATE PROCEDURE','CREATE TABLE', 'CREATE CONTEXT'" --set define off +begin + $if $$self_testing_install $then + execute immediate 'create or replace context &&ut3_owner._info using &&ut3_owner..ut_session_context'; + $else + execute immediate 'create or replace context ut3_info using &&ut3_owner..ut_session_context'; + $end +end; +/ --dbms_output buffer cache table @@install_component.sql 'core/ut_dbms_output_cache.sql' @@ -98,6 +106,11 @@ alter session set current_schema = &&ut3_owner; @@install_component.sql 'expectations/data_values/ut_key_anyval_pairs.tps' @@install_component.sql 'expectations/data_values/ut_key_anyvalues.tps' +--session_context +@@install_component.sql 'core/session_context/ut_session_context.pks' +@@install_component.sql 'core/session_context/ut_session_context.pkb' +@@install_component.sql 'core/session_context/ut_session_info.tps' +@@install_component.sql 'core/session_context/ut_session_info.tpb' --output buffer table @@install_component.sql 'core/output_buffers/ut_output_buffer_info_tmp.sql' diff --git a/source/uninstall_objects.sql b/source/uninstall_objects.sql index 4502f88a2..c905153f4 100644 --- a/source/uninstall_objects.sql +++ b/source/uninstall_objects.sql @@ -307,6 +307,10 @@ drop table ut_output_clob_buffer_tmp purge; drop table ut_output_buffer_info_tmp purge; +drop package ut_session_context; + +drop type ut_session_info force; + drop type ut_output_data_rows force; drop type ut_output_data_row force; diff --git a/test/install_ut3_user_tests.sql b/test/install_ut3_user_tests.sql index ef50432d1..05f2634e8 100644 --- a/test/install_ut3_user_tests.sql +++ b/test/install_ut3_user_tests.sql @@ -12,6 +12,8 @@ prompt Install user tests @@ut3_user/helpers/some_items.tps @@ut3_user/helpers/some_object.tps @@ut3_user/test_user.pks +@@ut3_user/expectations.pks +@@ut3_user/expectations.pkb @@ut3_user/expectations/unary/test_expect_not_to_be_null.pks @@ut3_user/expectations/unary/test_expect_to_be_null.pks @@ut3_user/expectations/unary/test_expect_to_be_empty.pks diff --git a/test/ut3_tester_helper/main_helper.pkb b/test/ut3_tester_helper/main_helper.pkb index 2370f1db6..c684054a1 100644 --- a/test/ut3_tester_helper/main_helper.pkb +++ b/test/ut3_tester_helper/main_helper.pkb @@ -153,6 +153,16 @@ create or replace package body main_helper is begin ut3.ut_utils.append_to_list(a_list,a_items); end; - + + procedure set_ut_run_context is + begin + ut3.ut_session_context.set_context('RUN_PATHS',' '); + end; + + procedure clear_ut_run_context is + begin + ut3.ut_session_context.clear_all_context; + end; + end; / diff --git a/test/ut3_tester_helper/main_helper.pks b/test/ut3_tester_helper/main_helper.pks index 7d349151c..63c99936c 100644 --- a/test/ut3_tester_helper/main_helper.pks +++ b/test/ut3_tester_helper/main_helper.pks @@ -44,6 +44,10 @@ create or replace package main_helper is procedure append_to_list(a_list in out nocopy ut3.ut_varchar2_rows, a_item clob); procedure append_to_list(a_list in out nocopy ut3.ut_varchar2_rows, a_items ut3.ut_varchar2_rows); + + procedure set_ut_run_context; + + procedure clear_ut_run_context; end; / diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 3fb2fbfec..41e35a03d 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -1,6 +1,11 @@ create or replace package body test_ut_run is - g_owner varchar2(250) := sys_context('userenv', 'current_schema'); + gc_owner constant varchar2(250) := sys_context('userenv', 'current_schema'); + gc_module constant varchar2(32767) := 'test module'; + gc_action constant varchar2(32767) := 'test action'; + gc_client_info constant varchar2(32767) := 'test client info'; + + g_context_test_results clob; procedure clear_expectations is begin @@ -746,7 +751,7 @@ Failures:% l_expected clob; begin select * bulk collect into l_results - from table ( ut3.ut.run( g_owner||'.'||g_owner ) ); + from table ( ut3.ut.run( gc_owner||'.'||gc_owner ) ); l_expected := '%1 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s)%'; ut.expect(ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( l_expected ); end; @@ -755,7 +760,7 @@ Failures:% pragma autonomous_transaction; begin execute immediate ' - create or replace package '||g_owner||'.'||g_owner||' as + create or replace package '||gc_owner||'.'||gc_owner||' as --%suite --%test @@ -763,7 +768,7 @@ Failures:% end;'; execute immediate ' - create or replace package body '||g_owner||'.'||g_owner||' as + create or replace package body '||gc_owner||'.'||gc_owner||' as procedure sample_test is begin ut.expect(1).to_equal(1); end; end;'; @@ -772,7 +777,7 @@ Failures:% procedure drop_schema_name_package is pragma autonomous_transaction; begin - execute immediate 'drop package '||g_owner||'.'||g_owner; + execute immediate 'drop package '||gc_owner||'.'||gc_owner; end; procedure run_with_random_order is @@ -1015,6 +1020,378 @@ Failures:% 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 set_application_info is + begin + dbms_application_info.set_module( gc_module, gc_action ); + dbms_application_info.set_client_info( gc_client_info ); + end; + + procedure create_context_test_suite is + pragma autonomous_transaction; + begin + execute immediate q'[ + create or replace package check_context is + --%suite(Suite description) + --%suitepath(some.suite.path) + + --%beforeall + procedure before_suite; + + --%context(some_context) + + --%displayname(context description) + + --%beforeall + procedure before_context; + + --%beforeeach + procedure before_each_test; + + --%test(Some test description) + --%beforetest(before_test) + --%aftertest(after_test) + procedure the_test; + procedure before_test; + procedure after_test; + + --%aftereach + procedure after_each_test; + + --%afterall + procedure after_context; + + --%endcontext + + + --%afterall + procedure after_suite; + + end;]'; + execute immediate q'[ + create or replace package body check_context is + + procedure print_context( a_procedure_name varchar2 ) is + l_results ut_varchar2_rows; + l_module varchar2(32767); + l_action varchar2(32767); + l_client_info varchar2(32767); + begin + select attribute||'='||value + bulk collect into l_results + from session_context where namespace = 'UT3_INFO' + order by attribute; + for i in 1 .. l_results.count loop + dbms_output.put_line( upper(a_procedure_name) ||':'|| l_results(i) ); + end loop; + dbms_application_info.read_module( l_module, l_action ); + dbms_application_info.read_client_info( l_client_info ); + + dbms_output.put_line( 'APPLICATION_INFO:MODULE=' || l_module ); + dbms_output.put_line( 'APPLICATION_INFO:ACTION=' || l_action ); + dbms_output.put_line( 'APPLICATION_INFO:CLIENT_INFO=' || l_client_info ); + end; + + procedure before_suite is + begin + print_context('before_suite'); + end; + + procedure before_context is + begin + print_context('before_context'); + end; + + procedure before_each_test is + begin + print_context('before_each_test'); + end; + + procedure the_test is + begin + print_context('the_test'); + end; + + procedure before_test is + begin + print_context('before_test'); + end; + + procedure after_test is + begin + print_context('after_test'); + end; + + procedure after_each_test is + begin + print_context('after_each_test'); + end; + + procedure after_context is + begin + print_context('after_context'); + end; + + procedure after_suite is + begin + print_context('after_suite'); + end; + + end;]'; + end; + + procedure drop_context_test_suite is + pragma autonomous_transaction; + begin + execute immediate q'[drop package check_context]'; + end; + + procedure run_context_test_suite is + l_lines ut3.ut_varchar2_list; + begin + select * bulk collect into l_lines from table(ut3.ut.run('check_context')); + g_context_test_results := ut3_tester_helper.main_helper.table_to_clob(l_lines); + end; + + + procedure sys_ctx_on_suite_beforeall is + begin + ut.expect(g_context_test_results).to_be_like( + '%BEFORE_SUITE:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.before_suite' + ||'%BEFORE_SUITE:CURRENT_EXECUTABLE_TYPE=beforeall' + ||'%BEFORE_SUITE:RUN_PATHS=check_context' + ||'%BEFORE_SUITE:SUITE_DESCRIPTION=Suite description' + ||'%BEFORE_SUITE:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%BEFORE_SUITE:SUITE_PATH=some.suite.path.check_context' + ||'%BEFORE_SUITE:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_SUITE:TIME_IN_SUITE=+000000000 00:00:0' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=before_suite%' + ); + ut.expect(g_context_test_results).not_to_be_like('%BEFORE_SUITE:CONTEXT_%'); + ut.expect(g_context_test_results).not_to_be_like('%BEFORE_SUITE:TEST_%'); + end; + + procedure sys_ctx_on_context_beforeall is + begin + ut.expect(g_context_test_results).to_be_like( + '%BEFORE_CONTEXT:CONTEXT_DESCRIPTION=context description' + ||'%BEFORE_CONTEXT:CONTEXT_NAME=some_context' + ||'%BEFORE_CONTEXT:CONTEXT_PATH=some.suite.path.check_context.some_context' + ||'%BEFORE_CONTEXT:CONTEXT_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_CONTEXT:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.before_context' + ||'%BEFORE_CONTEXT:CURRENT_EXECUTABLE_TYPE=beforeall' + ||'%BEFORE_CONTEXT:RUN_PATHS=check_context' + ||'%BEFORE_CONTEXT:SUITE_DESCRIPTION=Suite description' + ||'%BEFORE_CONTEXT:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%BEFORE_CONTEXT:SUITE_PATH=some.suite.path.check_context' + ||'%BEFORE_CONTEXT:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_CONTEXT:TIME_IN_CONTEXT=+000000000 00:00:0' + ||'%BEFORE_CONTEXT:TIME_IN_SUITE=+000000000 00:00:0' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=before_context%' + ); + ut.expect(g_context_test_results).not_to_be_like('%BEFORE_CONTEXT:TEST_%'); + end; + + procedure sys_ctx_on_beforeeach is + begin + ut.expect(g_context_test_results).to_be_like( + '%BEFORE_EACH_TEST:CONTEXT_DESCRIPTION=context description' + ||'%BEFORE_EACH_TEST:CONTEXT_NAME=some_context' + ||'%BEFORE_EACH_TEST:CONTEXT_PATH=some.suite.path.check_context.some_context' + ||'%BEFORE_EACH_TEST:CONTEXT_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_EACH_TEST:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.before_each_test' + ||'%BEFORE_EACH_TEST:CURRENT_EXECUTABLE_TYPE=beforeeach' + ||'%BEFORE_EACH_TEST:RUN_PATHS=check_context' + ||'%BEFORE_EACH_TEST:SUITE_DESCRIPTION=Suite description' + ||'%BEFORE_EACH_TEST:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%BEFORE_EACH_TEST:SUITE_PATH=some.suite.path.check_context' + ||'%BEFORE_EACH_TEST:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_EACH_TEST:TEST_DESCRIPTION=Some test description' + ||'%BEFORE_EACH_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' + ||'%BEFORE_EACH_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_EACH_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' + ||'%BEFORE_EACH_TEST:TIME_IN_SUITE=+000000000 00:00:0%' + ||'%BEFORE_EACH_TEST:TIME_IN_TEST=+000000000 00:00:0%' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=before_each_test%' + ); + end; + + procedure sys_ctx_on_beforetest is + begin + ut.expect(g_context_test_results).to_be_like( + '%BEFORE_TEST:CONTEXT_DESCRIPTION=context description' + ||'%BEFORE_TEST:CONTEXT_NAME=some_context' + ||'%BEFORE_TEST:CONTEXT_PATH=some.suite.path.check_context.some_context' + ||'%BEFORE_TEST:CONTEXT_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_TEST:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.before_test' + ||'%BEFORE_TEST:CURRENT_EXECUTABLE_TYPE=beforetest' + ||'%BEFORE_TEST:RUN_PATHS=check_context' + ||'%BEFORE_TEST:SUITE_DESCRIPTION=Suite description' + ||'%BEFORE_TEST:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%BEFORE_TEST:SUITE_PATH=some.suite.path.check_context' + ||'%BEFORE_TEST:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_TEST:TEST_DESCRIPTION=Some test description' + ||'%BEFORE_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' + ||'%BEFORE_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%BEFORE_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' + ||'%BEFORE_TEST:TIME_IN_SUITE=+000000000 00:00:0%' + ||'%BEFORE_TEST:TIME_IN_TEST=+000000000 00:00:0%' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=before_test%' + ); + end; + + procedure sys_ctx_on_test is + begin + ut.expect(g_context_test_results).to_be_like( + '%THE_TEST:CONTEXT_DESCRIPTION=context description' + ||'%THE_TEST:CONTEXT_NAME=some_context' + ||'%THE_TEST:CONTEXT_PATH=some.suite.path.check_context.some_context' + ||'%THE_TEST:CONTEXT_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%THE_TEST:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.the_test' + ||'%THE_TEST:CURRENT_EXECUTABLE_TYPE=test' + ||'%THE_TEST:RUN_PATHS=check_context' + ||'%THE_TEST:SUITE_DESCRIPTION=Suite description' + ||'%THE_TEST:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%THE_TEST:SUITE_PATH=some.suite.path.check_context' + ||'%THE_TEST:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%THE_TEST:TEST_DESCRIPTION=Some test description' + ||'%THE_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' + ||'%THE_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%THE_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' + ||'%THE_TEST:TIME_IN_SUITE=+000000000 00:00:0%' + ||'%THE_TEST:TIME_IN_TEST=+000000000 00:00:0%' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=the_test%' + ); + end; + + procedure sys_ctx_on_aftertest is + begin + ut.expect(g_context_test_results).to_be_like( + '%AFTER_TEST:CONTEXT_DESCRIPTION=context description' + ||'%AFTER_TEST:CONTEXT_NAME=some_context' + ||'%AFTER_TEST:CONTEXT_PATH=some.suite.path.check_context.some_context' + ||'%AFTER_TEST:CONTEXT_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_TEST:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.after_test' + ||'%AFTER_TEST:CURRENT_EXECUTABLE_TYPE=aftertest' + ||'%AFTER_TEST:RUN_PATHS=check_context' + ||'%AFTER_TEST:SUITE_DESCRIPTION=Suite description' + ||'%AFTER_TEST:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%AFTER_TEST:SUITE_PATH=some.suite.path.check_context' + ||'%AFTER_TEST:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_TEST:TEST_DESCRIPTION=Some test description' + ||'%AFTER_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' + ||'%AFTER_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' + ||'%AFTER_TEST:TIME_IN_SUITE=+000000000 00:00:0%' + ||'%AFTER_TEST:TIME_IN_TEST=+000000000 00:00:0%' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=after_test%' + ); + end; + + procedure sys_ctx_on_aftereach is + begin + ut.expect(g_context_test_results).to_be_like( + '%AFTER_EACH_TEST:CONTEXT_DESCRIPTION=context description' + ||'%AFTER_EACH_TEST:CONTEXT_NAME=some_context' + ||'%AFTER_EACH_TEST:CONTEXT_PATH=some.suite.path.check_context.some_context' + ||'%AFTER_EACH_TEST:CONTEXT_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_EACH_TEST:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.after_each_test' + ||'%AFTER_EACH_TEST:CURRENT_EXECUTABLE_TYPE=aftereach' + ||'%AFTER_EACH_TEST:RUN_PATHS=check_context' + ||'%AFTER_EACH_TEST:SUITE_DESCRIPTION=Suite description' + ||'%AFTER_EACH_TEST:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%AFTER_EACH_TEST:SUITE_PATH=some.suite.path.check_context' + ||'%AFTER_EACH_TEST:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_EACH_TEST:TEST_DESCRIPTION=Some test description' + ||'%AFTER_EACH_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' + ||'%AFTER_EACH_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_EACH_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' + ||'%AFTER_EACH_TEST:TIME_IN_SUITE=+000000000 00:00:0%' + ||'%AFTER_EACH_TEST:TIME_IN_TEST=+000000000 00:00:0%' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=after_each_test%' + ); + end; + + procedure sys_ctx_on_context_afterall is + begin + ut.expect(g_context_test_results).to_be_like( + '%AFTER_CONTEXT:CONTEXT_DESCRIPTION=context description' + ||'%AFTER_CONTEXT:CONTEXT_NAME=some_context' + ||'%AFTER_CONTEXT:CONTEXT_PATH=some.suite.path.check_context.some_context' + ||'%AFTER_CONTEXT:CONTEXT_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_CONTEXT:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.after_context' + ||'%AFTER_CONTEXT:CURRENT_EXECUTABLE_TYPE=afterall' + ||'%AFTER_CONTEXT:RUN_PATHS=check_context' + ||'%AFTER_CONTEXT:SUITE_DESCRIPTION=Suite description' + ||'%AFTER_CONTEXT:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%AFTER_CONTEXT:SUITE_PATH=some.suite.path.check_context' + ||'%AFTER_CONTEXT:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_CONTEXT:TIME_IN_CONTEXT=+000000000 00:00:0%' + ||'%AFTER_CONTEXT:TIME_IN_SUITE=+000000000 00:00:0%' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=after_context%' + ); + ut.expect(g_context_test_results).not_to_be_like('%AFTER_CONTEXT:TEST_%'); + end; + + procedure sys_ctx_on_suite_afterall is + begin + ut.expect(g_context_test_results).to_be_like( + '%AFTER_SUITE:CURRENT_EXECUTABLE_NAME='||gc_owner||'.check_context.after_suite' + ||'%AFTER_SUITE:CURRENT_EXECUTABLE_TYPE=afterall' + ||'%AFTER_SUITE:RUN_PATHS=check_context' + ||'%AFTER_SUITE:SUITE_DESCRIPTION=Suite description' + ||'%AFTER_SUITE:SUITE_PACKAGE='||gc_owner||'.check_context' + ||'%AFTER_SUITE:SUITE_PATH=some.suite.path.check_context' + ||'%AFTER_SUITE:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') + ||'%AFTER_SUITE:TIME_IN_SUITE=+000000000 00:00:0%' + ||'%APPLICATION_INFO:MODULE=utPLSQL' + ||'%APPLICATION_INFO:ACTION=check_context' + ||'%APPLICATION_INFO:CLIENT_INFO=after_suite%' + ); + ut.expect(g_context_test_results).not_to_be_like('%AFTER_SUITE:CONTEXT_%'); + ut.expect(g_context_test_results).not_to_be_like('%AFTER_SUITE:TEST_%'); + end; + + procedure sys_ctx_clear_after_run is + l_actual sys_refcursor; + begin + open l_actual for + select attribute||'='||value + from session_context where namespace = 'UT3_INFO'; + + ut.expect(l_actual).to_be_empty(); + end; + + procedure app_info_restore_after_run is + l_module varchar2(32767); + l_action varchar2(32767); + l_client_info varchar2(32767); + begin + dbms_application_info.read_module( l_module, l_action ); + dbms_application_info.read_client_info( l_client_info ); + + ut.expect(l_module).to_equal(gc_module); + ut.expect(l_action).to_equal(gc_action); + --Disabled as it can't be tested. + --UT3_LATEST_RELEASE is also setting the client_info on each procedure + -- ut.expect(l_client_info).to_equal(gc_client_info); + end; + end; / diff --git a/test/ut3_user/api/test_ut_run.pks b/test/ut3_user/api/test_ut_run.pks index 406c70b09..38a6eee2c 100644 --- a/test/ut3_user/api/test_ut_run.pks +++ b/test/ut3_user/api/test_ut_run.pks @@ -11,7 +11,8 @@ create or replace package test_ut_run is procedure ut_version; --%test(ut.fail() marks test as failed) - --%aftertest(clear_expectations) + --%beforetest(ut3_tester_helper.main_helper.set_ut_run_context) + --%aftertest(clear_expectations, ut3_tester_helper.main_helper.clear_ut_run_context) procedure ut_fail; --%context(ut_run_procedure) @@ -228,6 +229,54 @@ create or replace package test_ut_run is procedure tag_run_func_path_list; --%endcontext + + --%context(ut3_info context) + + --%beforeall + procedure set_application_info; + --%beforeall + procedure create_context_test_suite; + + --%beforeall + procedure run_context_test_suite; + + --%afterall + procedure drop_context_test_suite; + + --%test(sets context for suite level beforeall) + procedure sys_ctx_on_suite_beforeall; + + --%test(sets context for context level beforeall) + procedure sys_ctx_on_context_beforeall; + + --%test(set for context level beforeeach) + procedure sys_ctx_on_beforeeach; + + --%test(set for context level beforetest) + procedure sys_ctx_on_beforetest; + + --%test(set for context level test) + procedure sys_ctx_on_test; + + --%test(set for context level aftertest) + procedure sys_ctx_on_aftertest; + + --%test(set for context level aftereach) + procedure sys_ctx_on_aftereach; + + --%test(set for context level afterall) + procedure sys_ctx_on_context_afterall; + + --%test(set for suite level afterall) + procedure sys_ctx_on_suite_afterall; + + --%test(is cleared after run) + procedure sys_ctx_clear_after_run; + + --%test(application info is restored after run) + procedure app_info_restore_after_run; + + --%endcontext end; / diff --git a/test/ut3_user/expectations.pkb b/test/ut3_user/expectations.pkb new file mode 100644 index 000000000..c1f07dd9f --- /dev/null +++ b/test/ut3_user/expectations.pkb @@ -0,0 +1,31 @@ +create or replace package body expectations as + + --%test(Expectations return data to screen when called standalone) + + procedure inline_expectation_to_dbms_out is + l_expected sys_refcursor; + l_actual sys_refcursor; + l_output dbmsoutput_linesarray; + l_results ut3.ut_varchar2_list; + l_lines number := 10000; + begin + --Arrange + ut3_tester_helper.main_helper.clear_ut_run_context; + open l_expected for + select 'FAILURE' as out_row from dual union all + select 'Actual: 1 (number) was expected to equal: 0 (number)' from dual union all + select 'SUCCESS' from dual union all + select 'Actual: 0 (number) was expected to equal: 0 (number)' from dual union all + select '' from dual; + --Act + ut3.ut.expect(1).to_equal(0); + ut3.ut.expect(0).to_equal(0); + + --Assert + dbms_output.get_lines(lines => l_output, numlines => l_lines); + open l_actual for select trim(column_value) as out_row from table(l_output); + + ut.expect(l_actual).to_equal(l_expected); + end; +end; +/ \ No newline at end of file diff --git a/test/ut3_user/expectations.pks b/test/ut3_user/expectations.pks new file mode 100644 index 000000000..ab8bd3835 --- /dev/null +++ b/test/ut3_user/expectations.pks @@ -0,0 +1,14 @@ +create or replace package expectations as + --%suite + --%suitepath(utplsql.test_user) + + --%beforeall(ut3_tester_helper.main_helper.set_ut_run_context) + + --%afterall(ut3_tester_helper.main_helper.clear_ut_run_context) + + --%test(Expectations return data to screen when called standalone) + --%aftertest(ut3_tester_helper.main_helper.set_ut_run_context) + procedure inline_expectation_to_dbms_out; + +end; +/ \ No newline at end of file From 99bae42e69b07c751fb57ce0922b05fae2b94347 Mon Sep 17 00:00:00 2001 From: Jacek Gebal Date: Wed, 3 Jul 2019 21:17:44 +0100 Subject: [PATCH 2/7] Removed `time_in_suite`, `time_in_context`, `time_in_test` as they are of low value and could bing confusion. --- .../core/session_context/ut_session_info.tpb | 24 +++---------------- .../core/session_context/ut_session_info.tps | 3 --- test/ut3_user/api/test_ut_run.pkb | 21 ---------------- 3 files changed, 3 insertions(+), 45 deletions(-) diff --git a/source/core/session_context/ut_session_info.tpb b/source/core/session_context/ut_session_info.tpb index 398ff0067..2d3fdbed9 100644 --- a/source/core/session_context/ut_session_info.tpb +++ b/source/core/session_context/ut_session_info.tpb @@ -42,18 +42,16 @@ create or replace type body ut_session_info as member procedure before_calling_suite(self in out nocopy ut_session_info, a_suite in ut_logical_suite) is begin if a_suite is not of (ut_suite_context) then - suite_start_time := a_suite.start_time; ut_session_context.set_context( 'suite_path', a_suite.path ); ut_session_context.set_context( 'suite_package', a_suite.object_owner||'.'||a_suite.object_name ); ut_session_context.set_context( 'suite_description', a_suite.description ); - ut_session_context.set_context( 'suite_start_time', ut_utils.to_string(suite_start_time) ); + ut_session_context.set_context( 'suite_start_time', ut_utils.to_string(a_suite.start_time) ); dbms_application_info.set_module( 'utPLSQL', a_suite.object_name ); else - context_start_time := a_suite.start_time; ut_session_context.set_context( 'context_name', a_suite.name ); ut_session_context.set_context( 'context_path', a_suite.path); ut_session_context.set_context( 'context_description', a_suite.description ); - ut_session_context.set_context( 'context_start_time', ut_utils.to_string(context_start_time) ); + ut_session_context.set_context( 'context_start_time', ut_utils.to_string(a_suite.start_time) ); end if; end; @@ -64,25 +62,20 @@ create or replace type body ut_session_info as ut_session_context.clear_context( 'suite_path' ); ut_session_context.clear_context( 'suite_description' ); ut_session_context.clear_context( 'suite_start_time' ); - ut_session_context.clear_context( 'time_in_suite' ); - suite_start_time := null; else ut_session_context.clear_context( 'context_name' ); ut_session_context.clear_context( 'context_path' ); ut_session_context.clear_context( 'context_description' ); ut_session_context.clear_context( 'context_start_time' ); - ut_session_context.clear_context( 'time_in_context' ); - context_start_time := null; end if; end; member procedure before_calling_test(self in out nocopy ut_session_info, a_test in ut_test) is begin - test_start_time := a_test.start_time; ut_session_context.set_context( 'test_name', a_test.object_owner||'.'||a_test.object_name||'.'||a_test.name ); ut_session_context.set_context( 'test_description', a_test.description ); - ut_session_context.set_context( 'test_start_time', ut_utils.to_string(test_start_time) ); + ut_session_context.set_context( 'test_start_time', ut_utils.to_string(a_test.start_time) ); end; member procedure after_calling_test (self in out nocopy ut_session_info, a_test in ut_test) is @@ -90,8 +83,6 @@ create or replace type body ut_session_info as ut_session_context.clear_context( 'test_name' ); ut_session_context.clear_context( 'test_description' ); ut_session_context.clear_context( 'test_start_time' ); - ut_session_context.clear_context( 'time_in_test' ); - test_start_time := null; end; member procedure before_calling_executable(self in out nocopy ut_session_info, a_executable in ut_executable) is @@ -102,15 +93,6 @@ create or replace type body ut_session_info as a_executable.owner_name||'.'||a_executable.object_name||'.'||a_executable.procedure_name ); dbms_application_info.set_client_info( a_executable.procedure_name ); - if suite_start_time is not null then - ut_session_context.set_context( 'time_in_suite', current_timestamp - suite_start_time ); - if context_start_time is not null then - ut_session_context.set_context( 'time_in_context', current_timestamp - context_start_time ); - end if; - if test_start_time is not null then - ut_session_context.set_context( 'time_in_test', current_timestamp - test_start_time ); - end if; - end if; end; member procedure after_calling_executable(self in out nocopy ut_session_info, a_executable in ut_executable) is diff --git a/source/core/session_context/ut_session_info.tps b/source/core/session_context/ut_session_info.tps index 451d7b8f6..863b0dc3e 100644 --- a/source/core/session_context/ut_session_info.tps +++ b/source/core/session_context/ut_session_info.tps @@ -19,9 +19,6 @@ create or replace type ut_session_info under ut_event_listener ( module varchar2(4000), action varchar2(4000), client_info varchar2(4000), - suite_start_time timestamp, - context_start_time timestamp, - test_start_time timestamp, constructor function ut_session_info(self in out nocopy ut_session_info) return self as result, member procedure before_calling_run(self in out nocopy ut_session_info, a_run in ut_run), diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 41e35a03d..329b71357 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -1164,7 +1164,6 @@ Failures:% ||'%BEFORE_SUITE:SUITE_PACKAGE='||gc_owner||'.check_context' ||'%BEFORE_SUITE:SUITE_PATH=some.suite.path.check_context' ||'%BEFORE_SUITE:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%BEFORE_SUITE:TIME_IN_SUITE=+000000000 00:00:0' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=before_suite%' @@ -1187,8 +1186,6 @@ Failures:% ||'%BEFORE_CONTEXT:SUITE_PACKAGE='||gc_owner||'.check_context' ||'%BEFORE_CONTEXT:SUITE_PATH=some.suite.path.check_context' ||'%BEFORE_CONTEXT:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%BEFORE_CONTEXT:TIME_IN_CONTEXT=+000000000 00:00:0' - ||'%BEFORE_CONTEXT:TIME_IN_SUITE=+000000000 00:00:0' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=before_context%' @@ -1213,9 +1210,6 @@ Failures:% ||'%BEFORE_EACH_TEST:TEST_DESCRIPTION=Some test description' ||'%BEFORE_EACH_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' ||'%BEFORE_EACH_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%BEFORE_EACH_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' - ||'%BEFORE_EACH_TEST:TIME_IN_SUITE=+000000000 00:00:0%' - ||'%BEFORE_EACH_TEST:TIME_IN_TEST=+000000000 00:00:0%' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=before_each_test%' @@ -1239,9 +1233,6 @@ Failures:% ||'%BEFORE_TEST:TEST_DESCRIPTION=Some test description' ||'%BEFORE_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' ||'%BEFORE_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%BEFORE_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' - ||'%BEFORE_TEST:TIME_IN_SUITE=+000000000 00:00:0%' - ||'%BEFORE_TEST:TIME_IN_TEST=+000000000 00:00:0%' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=before_test%' @@ -1265,9 +1256,6 @@ Failures:% ||'%THE_TEST:TEST_DESCRIPTION=Some test description' ||'%THE_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' ||'%THE_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%THE_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' - ||'%THE_TEST:TIME_IN_SUITE=+000000000 00:00:0%' - ||'%THE_TEST:TIME_IN_TEST=+000000000 00:00:0%' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=the_test%' @@ -1291,9 +1279,6 @@ Failures:% ||'%AFTER_TEST:TEST_DESCRIPTION=Some test description' ||'%AFTER_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' ||'%AFTER_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%AFTER_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' - ||'%AFTER_TEST:TIME_IN_SUITE=+000000000 00:00:0%' - ||'%AFTER_TEST:TIME_IN_TEST=+000000000 00:00:0%' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=after_test%' @@ -1317,9 +1302,6 @@ Failures:% ||'%AFTER_EACH_TEST:TEST_DESCRIPTION=Some test description' ||'%AFTER_EACH_TEST:TEST_NAME='||gc_owner||'.check_context.the_test' ||'%AFTER_EACH_TEST:TEST_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%AFTER_EACH_TEST:TIME_IN_CONTEXT=+000000000 00:00:0%' - ||'%AFTER_EACH_TEST:TIME_IN_SUITE=+000000000 00:00:0%' - ||'%AFTER_EACH_TEST:TIME_IN_TEST=+000000000 00:00:0%' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=after_each_test%' @@ -1340,8 +1322,6 @@ Failures:% ||'%AFTER_CONTEXT:SUITE_PACKAGE='||gc_owner||'.check_context' ||'%AFTER_CONTEXT:SUITE_PATH=some.suite.path.check_context' ||'%AFTER_CONTEXT:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%AFTER_CONTEXT:TIME_IN_CONTEXT=+000000000 00:00:0%' - ||'%AFTER_CONTEXT:TIME_IN_SUITE=+000000000 00:00:0%' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=after_context%' @@ -1359,7 +1339,6 @@ Failures:% ||'%AFTER_SUITE:SUITE_PACKAGE='||gc_owner||'.check_context' ||'%AFTER_SUITE:SUITE_PATH=some.suite.path.check_context' ||'%AFTER_SUITE:SUITE_START_TIME='||to_char(current_timestamp,'yyyy-mm-dd"T"hh24:mi') - ||'%AFTER_SUITE:TIME_IN_SUITE=+000000000 00:00:0%' ||'%APPLICATION_INFO:MODULE=utPLSQL' ||'%APPLICATION_INFO:ACTION=check_context' ||'%APPLICATION_INFO:CLIENT_INFO=after_suite%' From d932f757c65ba49c14a39a3939c6e5e9b1e1c93e Mon Sep 17 00:00:00 2001 From: Jacek Gebal Date: Mon, 8 Jul 2019 01:09:20 +0100 Subject: [PATCH 3/7] - Fixed minor typos, formatting and issues with expectation results. - Changed how stack trace is reported on expectations to provide more information to the user. - Fixed expectation messages for successful results - moved `to_be_empty` / `not_to_be_empty` to base `ut_expectation` type as it's available for `clob`/`blob` types too. - added-back grant on `ut_matcher` to allow for passing various matchers by users. - fixed issue with misleading message: `All rows are different as the columns position is not matching` when comparing a null collection/cursor {WIP] Updates to documentation for expectations. --- docs/userguide/expectations.md | 1231 ++++++++++------- source/core/types/ut_expectation_result.tpb | 2 +- source/core/ut_expectation_processor.pkb | 56 +- source/create_grants.sql | 1 + source/create_synonyms.sql | 1 + .../data_values/ut_compound_data_helper.pkb | 2 +- .../data_values/ut_compound_data_value.tpb | 8 +- .../data_values/ut_compound_data_value.tps | 3 +- .../data_values/ut_data_value.tpb | 2 +- .../data_values/ut_data_value_anydata.tpb | 2 +- .../data_values/ut_data_value_json.tpb | 7 +- .../data_values/ut_data_value_refcursor.tpb | 11 +- source/expectations/matchers/ut_contain.tpb | 3 +- source/expectations/matchers/ut_equal.tpb | 19 +- source/expectations/matchers/ut_matcher.tpb | 4 +- source/expectations/ut_expectation.tpb | 10 + source/expectations/ut_expectation.tps | 3 + .../expectations/ut_expectation_compound.tpb | 10 - .../expectations/ut_expectation_compound.tps | 2 - source/expectations/ut_expectation_json.tpb | 10 - source/expectations/ut_expectation_json.tps | 2 - .../test_expectation_processor.pkb | 46 +- .../test_expectation_processor.pks | 11 +- test/ut3_user/expectations.pkb | 49 +- test/ut3_user/expectations.pks | 6 +- .../expectations/test_expectation_anydata.pkb | 11 +- .../expectations/test_expectations_cursor.pkb | 6 +- .../unary/test_expect_to_be_empty.pkb | 2 +- .../reporters/test_realtime_reporter.pkb | 2 +- .../reporters/test_teamcity_reporter.pkb | 4 +- 30 files changed, 920 insertions(+), 606 deletions(-) diff --git a/docs/userguide/expectations.md b/docs/userguide/expectations.md index 970237723..6f28325a2 100644 --- a/docs/userguide/expectations.md +++ b/docs/userguide/expectations.md @@ -2,93 +2,263 @@ # 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 a combination of expectation and matcher to perform the check on the data. +utPLSQL uses expectations and matchers to perform the check on the data. -Example of a unit test procedure body. +Example of an expectation ```sql begin - ut.expect( 'the tested value', 'optional custom failure message' ).to_( equal('the expected value') ); + ut.expect( 'the tested value' ).to_equal('the expected value'); end; +/ ``` -Expectation is a set of the expected value(s), actual values(s) and the matcher(s) to run on those values. -You can also add a custom failure message for an expectation. +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: 'the tested value' (varchar2) was expected to equal: 'the expected value' (varchar2) + at "anonymous block", line 2 +``` -Matcher defines the comparison operation to be performed on expected and actual values. +Expectation is a combination of: +- the expected value +- optional custom message for the expectation +- the matcher used to perform comparison +- them matcher parameters (actual value), depending on the matcher type + +**Note:** +> The output from expectation is provided directly DBMS_OUTPUT only when the expectation is executed standalone (not as part of unit test). +> 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. + +Matcher defines the comparison operation to be performed on expected (and actual) value. Pseudo-code: ```sql ut.expect( a_actual {data-type} [, a_message {varchar2}] ).to_( {matcher} ); ut.expect( a_actual {data-type} [, a_message {varchar2}] ).not_to( {matcher} ); ``` -All matchers have shortcuts like below, sou you don't need to surround matcher with brackets, unless you want to pass it as parameter to the expectation. +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 - ut.expect( a_actual {data-type} ).to_{matcher}; - ut.expect( a_actual {data-type} ).not_to_{matcher}; + ut.expect( a_actual ).to_( {matcher} ); + ut.expect( a_actual ).not_to( {matcher} ); + -- example + ut.expect( 1 ).to_( be_null() ); +``` + +Shortcut syntax, where matcher is directly part of expectation: +```sql + ut.expect( a_actual ).to_{matcher}; + ut.expect( a_actual ).not_to_{matcher}; + + --example + 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 +declare + procedure do_check( p_actual varchar2, p_matcher ut_matcher ) is + begin + ut.expect(p_actual).to_( p_matcher ); + end; +begin + do_check( 'a', equal('b') ); + do_check( 'Alibaba', match('ali','i') ); +end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: 'a' (varchar2) was expected to equal: 'b' (varchar2) + at "anonymous block", line 4 + at "anonymous block", line 7 +SUCCESS + Actual: 'Alibaba' (varchar2) was expected to match: 'ali' , modifiers 'i' ``` -## Providing a custom failure message -You can provide a custom failure message as second argument for the expectation. +**Note:** +> The examples in the document will be only using shortcut syntax, to keep the document brief. + +# 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} )` +- `be_empty()` +- `be_false()` +- `be_greater_than( a_expected {data-type} )` +- `be_greater_or_equal( a_expected {data-type} )` +- `be_less_or_equal( a_expected {data-type} )` +- `be_less_than( a_expected {data-type} )` +- `be_like( a_mask {varchar2} [, a_escape_char {varchar2}] )` +- `be_not_null()` +- `be_null()` +- `be_true()` +- `equal( a_expected {data-type} [, a_nulls_are_equal {boolean}] )` +- `contain( a_expected {data-type})` +- `have_count( a_expected {integer} )` +- `match( a_patter {varchar2} [, a_modifiers {varchar2}] )` + +## Providing a custom message +You can provide a custom failure message as second argument for the expectation by passing message as the second parameter to the expectation. +`ut.expect( a_actual {data-type}, a_message {varchar2} ).to_{matcher}` + +Example: ````sql - -- Pseudocode - ut.expect( a_actual {data-type}, a_message {varchar2} ).to_{matcher}; - -- Example - ut.expect( 'supercat', 'checked superhero-animal was not a dog' ).to_( equal('superdog') ); +exec ut.expect( 'supercat', 'checked superhero-animal was not a dog' ).to_equal('superdog'); ```` +Returns following output via DBMS_OUTPUT: +``` +FAILURE + "checked superhero-animal was not a dog" + Actual: 'supercat' (varchar2) was expected to equal: 'superdog' (varchar2) + at "anonymous block", line 1 +``` If the message is provided, it is being added to the normal failure message returned by the matcher. - This is mostly useful when your expectations accept dynamic content, as you can provide additional context to make failing test results more readable. -### Dynamic tests example -You have a bunch of tables and an archive functionality for them and you want to test if the things you put into live-tables are removed from live-tables and present in archive-tables. +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 -````sql -procedure test_data_existance( i_tableName varchar2 ) - as - v_count_real integer; - v_count_archive integer; - begin - - execute immediate 'select count(*) from ' || i_tablename || '' into v_count_real; - execute immediate 'select count(*) from ' || i_tablename || '_ARCHIVE' into v_count_archive; - - ut.expect( v_count_archive, 'failure checking entry-count of ' || i_tablename || '_archive' ).to_( equal(1) ); - ut.expect( v_count_real, 'failure checking entry-count of ' || i_tablename ).to_( equal(0) ); +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 +create or replace package shared_expectation_test is + --%suite + + --%test + procedure the_test; +end; +/ +create or replace package body shared_expectation_test is + procedure table_is_empty(p_table_name varchar2) is + l_count integer; + begin + execute immediate 'select count(*) from '||p_table_name into l_count; + ut.expect( l_count, 'Checking table '||p_table_name ).to_equal(0); end; - - procedure test_archive_data - as + + procedure the_test is begin - -- Arrange - -- insert several data into real-tables here + table_is_empty('ALL_USERS'); + table_is_empty('ALL_TABLES'); + end; +end; +/ - -- Act - package_to_test.archive_data(); +exec ut.run('shared_expectation_test'); +``` - -- Assert - test_data_existance('TABLE_A'); - test_data_existance('TABLE_B'); - test_data_existance('TABLE_C'); - test_data_existance('TABLE_D'); -end; -```` -A failed output will look like this: -```` +Returns following output via DBMS_OUTPUT: +``` +shared_expectation_test + the_test [.064 sec] (FAILED - 1) + Failures: - 1) test_archive_data - "failure checking entry-count of table_a_archive" - Actual: 2 (number) was expected to equal: 1 (number) - at "UT_TEST_PACKAGE.TEST_DATA_EXISTANCE", line 12 ut.expect( v_count_archive, 'failure checking entry-count of ' || i_tablename || '_archive' ).to_( equal(1) ); -```` + 1) the_test + "Checking table ALL_USERS" + Actual: 28 (number) was expected to equal: 0 (number) + at "UT3$USER#.SHARED_EXPECTATION_TEST.TABLE_IS_EMPTY", line 6 ut.expect( l_count, 'Checking table '||p_table_name ).to_equal(0); + at "UT3$USER#.SHARED_EXPECTATION_TEST.THE_TEST", line 11 + + "Checking table ALL_TABLES" + Actual: 55 (number) was expected to equal: 0 (number) + at "UT3$USER#.SHARED_EXPECTATION_TEST.TABLE_IS_EMPTY", line 6 ut.expect( l_count, 'Checking table '||p_table_name ).to_equal(0); + at "UT3$USER#.SHARED_EXPECTATION_TEST.THE_TEST", line 12 + +Finished in .066344 seconds +1 tests, 1 failed, 0 errored, 0 disabled, 0 warning(s) +``` + +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 +- the line text of the expectations +- the call stack for the expectation (in the example it's the lines that called the procedure `table_is_empty`) + +## Negating a matcher + +Expectations provide a very convenient way to perform a check on a negated matcher. + +Syntax to check for matcher evaluating to true: +```sql +begin + ut.expect( a_actual {data-type} ).to_{matcher}; + ut.expect( a_actual {data-type} ).to_( {matcher} ); +end; +``` + +Syntax to check for matcher evaluating to false: +```sql +begin + ut.expect( a_actual {data-type} ).not_to_{matcher}; + ut.expect( a_actual {data-type} ).not_to( {matcher} ); +end; +``` + +If a matcher evaluated to NULL, then both `to_` and `not_to` will cause the expectation to report failure. + +Example: +```sql +declare + l_actual boolean; +begin + ut.expect( l_actual ).to_be_true(); + ut.expect( l_actual ).not_to_be_true(); +end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: NULL (boolean) was expected to be true + at "anonymous block", line 4 +FAILURE + Actual: NULL (boolean) was expected not to be true + at "anonymous block", line 5 +``` +Since NULL is neither *true* nor *false*, both expectations will report failure. + +# Supported data types + +The matrix below illustrates the data types supported by different matchers. + +| Matcher | blob | boolean | clob | date | number | timestamp | timestamp
with
timezone | timestamp
with
local
timezone | varchar2 | interval
year
to
month | interval
day
to
second | cursor | nested
table
/ varray | object | json | +| :---------------------: | :--: | :-----: | :--: | :--: | :----: | :-------: | :---------------------------: | :------------------------------------: | :------: | :-----------------------------: | :-----------------------------: | :----: | :-------------------------: | :----: | :--: | +| **be_not_null** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | +| **be_null** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | +| **be_false** | | X | | | | | | | | | | | | | | +| **be_true** | | X | | | | | | | | | | | | | | +| **be_greater_than** | | | | X | X | X | X | X | | X | X | | | | | +| **be_greater_or_equal** | | | | X | X | X | X | X | | X | X | | | | | +| **be_less_or_equal** | | | | X | X | X | X | X | | X | X | | | | | +| **be_less_than** | | | | X | X | X | X | X | | X | X | | | | | +| **be_between** | | | | X | X | X | X | X | X | X | X | | | | | +| **equal** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | +| **contain** | | | | | | | | | | | | X | X | X | | +| **match** | | | X | | | | | | X | | | | | | | +| **be_like** | | | X | | | | | | X | | | | | | | +| **be_empty** | X | | X | | | | | | | | | X | X | | X | +| **have_count** | | | | | | | | | | | | X | X | | X | + # Expecting exceptions -Testing is not limited to checking for happy-path scenarios. When writing tests, you often want to check that in specific scenarios, an exception is thrown. +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 +Use the `--%throws` annotation, to test for expected exceptions. Example: ```sql @@ -101,9 +271,6 @@ end; create or replace package test_divide as --%suite(Divide function) - --%test(Return divided numbers) - procedure divides_numbers; - --%test(Throws divisor equal) --%throws(-01476) procedure raises_divisor_exception; @@ -112,11 +279,6 @@ end; create or replace package body test_divide is - procedure divides_numbers is - begin - ut.expect(divide(6,2)).to_equal(3); - end; - procedure raises_divisor_exception is x integer; begin @@ -129,40 +291,77 @@ end; exec ut.run('test_divide'); ``` -For details see documentation of the [`--%throws` annotation.](annotations.md#throws-annotation) +Returns following output via DBMS_OUTPUT: +``` +Divide function + Throws divisor equal [.007 sec] + +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) -# Matchers -utPLSQL provides the following matchers to perform checks on the expected and actual values. +# Matchers + +You can choose different matchers to validate the your PL/SQL code is working as expected. -- `be_between` -- `be_empty` -- `be_false` -- `be_greater_than` -- `be_greater_or_equal` -- `be_less_or_equal` -- `be_less_than` -- `be_like` -- `be_not_null` -- `be_null` -- `be_true` -- `equal` -- `contain` -- `have_count` -- `match` ## be_between Validates that the actual value is between the lower and upper bound. Example: ```sql +declare + l_timestamp timestamp := current_timestamp; + l_timestamp_tz timestamp with time zone := systimestamp; + l_timestamp_ltz timestamp with local time zone := systimestamp; + l_interval_ds interval day to second := interval '1' second; + l_interval_ym interval year to month := interval '1' year; begin - ut.expect( a_actual => 3 ).to_be_between( a_lower_bound => 1, a_upper_bound => 3 ); ut.expect( 3 ).to_be_between( 1, 3 ); - --or - ut.expect( a_actual => 3 ).to_( be_between( a_lower_bound => 1, a_upper_bound => 3 ) ); - ut.expect( 3 ).to_( be_between( 1, 3 ) ); + ut.expect( 5 ).to_( be_between( 1, 3 ) ); + ut.expect( 3 ).not_to_be_between( 1, 3 ); + ut.expect( 5 ).not_to( be_between( 1, 3 ) ); + ut.expect( sysdate ).to_be_between( sysdate, sysdate + 1 ); + ut.expect( l_timestamp ).to_be_between( l_timestamp, l_timestamp ); + ut.expect( systimestamp ).to_be_between( l_timestamp_tz, systimestamp ); + ut.expect( systimestamp ).to_be_between( l_timestamp_ltz, l_timestamp_ltz ); + ut.expect( l_interval_ds ).to_be_between( interval '0.1' second, interval '1' day ); + ut.expect( l_interval_ym ).to_be_between( interval '12' month, interval '12' year ); + ut.expect( 'Abb' ).to_be_between( 'Aba', 'Abc' ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: 3 (number) was expected to be between: 1 and 3 +FAILURE + Actual: 5 (number) was expected to be between: 1 and 3 + at "anonymous block", line 9 +FAILURE + Actual: 3 (number) was expected not to be between: 1 and 3 + at "anonymous block", line 10 +SUCCESS + Actual: 5 (number) was expected not to be between: 1 and 3 +SUCCESS + Actual: 2019-07-07T21:25:27 (date) was expected to be between: 2019-07-07T21:25:27 and 2019-07-08T21:25:27 +SUCCESS + Actual: 2019-07-07T22:25:27.701546000 (timestamp) was expected to be between: 2019-07-07T22:25:27.701546000 and 2019-07-07T22:25:27.701546000 +SUCCESS + Actual: 2019-07-07T21:25:27.705768000 +00:00 (timestamp with time zone) was expected to be between: 2019-07-07T21:25:27.701596000 +00:00 and 2019-07-07T21:25:27.705808000 +00:00 +FAILURE + The matcher 'be between' cannot be used with data type (timestamp with time zone). + at "anonymous block", line 15 +SUCCESS + Actual: +000000000 00:00:01.000000000 (interval day to second) was expected to be between: +000000000 00:00:00.100000000 and +000000001 00:00:00.000000000 +SUCCESS + Actual: +000000001-00 (interval year to month) was expected to be between: +000000001-00 and +000000012-00 +SUCCESS + Actual: 'Abb' (varchar2) was expected to be between: 'Aba' and 'Abc' ``` ## be_empty @@ -174,27 +373,44 @@ Can be used with `BLOB`,`CLOB`, `refcursor` or `nested table`/`varray` passed as BLOB/CLOB that is initialized is not NULL but it is actually equal to `empty_blob()`/`empty_clob()`. -Usage: +Example: ```sql -procedure test_if_cursor_is_empty is +declare l_cursor sys_refcursor; begin - open l_cursor for select * from dual where 1 = 0; + open l_cursor for select * from dual where 0=1; ut.expect( l_cursor ).to_be_empty(); - --or - ut.expect( l_cursor ).to_( be_empty() ); + ut.expect( anydata.convertCollection(ut_varchar2_list()) ).to_( be_empty() ); + ut.expect( empty_clob() ).not_to_be_empty(); + ut.expect( empty_blob() ).not_to( be_empty() ); + ut.expect( 1 ).not_to( be_empty() ); end; +/ ``` -```sql -procedure test_if_cursor_is_empty is - l_data ut_varchar2_list; -begin - l_data := ut_varchar2_list(); - ut.expect( anydata.convertCollection( l_data ) ).to_be_empty(); - --or - ut.expect( anydata.convertCollection( l_data ) ).to_( be_empty() ); -end; +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: (refcursor [ count = 0 ]) + Data-types: + VARCHAR2 + Data: + was expected to be empty +SUCCESS + Actual: (ut3.ut_varchar2_list [ count = 0 ]) + Data-types: + VARCHAR2 + Data: + was expected to be empty +FAILURE + Actual: EMPTY (clob) was expected not to be empty + at "anonymous block", line 7 +FAILURE + Actual: EMPTY (blob) was expected not to be empty + at "anonymous block", line 8 +FAILURE + The matcher 'be empty' cannot be used with data type (number). + at "anonymous block", line 9 ``` ## be_false @@ -204,9 +420,25 @@ Usage: ```sql begin ut.expect( ( 1 = 0 ) ).to_be_false(); - --or - ut.expect( ( 1 = 0 ) ).to_( be_false() ); + ut.expect( ( 1 = 1 ) ).to_( be_false() ); + ut.expect( ( 1 = 0 ) ).not_to_be_false(); + ut.expect( ( 1 = 1 ) ).not_to( be_false() ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: FALSE (boolean) was expected to be false +FAILURE + Actual: TRUE (boolean) was expected to be false + at "anonymous block", line 3 +FAILURE + Actual: FALSE (boolean) was expected not to be false + at "anonymous block", line 4 +SUCCESS + Actual: TRUE (boolean) was expected not to be false ``` ## be_greater_or_equal @@ -216,9 +448,25 @@ Usage: ```sql begin ut.expect( sysdate ).to_be_greater_or_equal( sysdate - 1 ); - --or - ut.expect( sysdate ).to_( be_greater_or_equal( sysdate - 1 ) ); + ut.expect( sysdate ).to_( be_greater_or_equal( sysdate + 1 ) ); + ut.expect( sysdate ).not_to_be_greater_or_equal( sysdate - 1 ); + ut.expect( sysdate ).not_to( be_greater_or_equal( sysdate + 1 ) ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: 2019-07-07T22:43:29 (date) was expected to be greater or equal: 2019-07-06T22:43:29 (date) +FAILURE + Actual: 2019-07-07T22:43:29 (date) was expected to be greater or equal: 2019-07-08T22:43:29 (date) + at "anonymous block", line 3 +FAILURE + Actual: 2019-07-07T22:43:29 (date) was expected not to be greater or equal: 2019-07-06T22:43:29 (date) + at "anonymous block", line 4 +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 @@ -228,9 +476,25 @@ Usage: ```sql begin ut.expect( 2 ).to_be_greater_than( 1 ); - --or - ut.expect( 2 ).to_( be_greater_than( 1 ) ); + ut.expect( 0 ).to_( be_greater_than( 1 ) ); + ut.expect( 2 ).not_to_be_greater_than( 1 ); + ut.expect( 0 ).not_to( be_greater_than( 1 ) ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: 2 (number) was expected to be greater than: 1 (number) +FAILURE + Actual: 0 (number) was expected to be greater than: 1 (number) + at "anonymous block", line 3 +FAILURE + Actual: 2 (number) was expected not to be greater than: 1 (number) + at "anonymous block", line 4 +SUCCESS + Actual: 0 (number) was expected not to be greater than: 1 (number) ``` ## be_less_or_equal @@ -240,9 +504,25 @@ Usage: ```sql begin ut.expect( 3 ).to_be_less_or_equal( 3 ); - --or - ut.expect( 3 ).to_( be_less_or_equal( 3 ) ); + ut.expect( 4 ).to_( be_less_or_equal( 3 ) ); + ut.expect( 3 ).not_to_be_less_or_equal( 3 ); + ut.expect( 4 ).not_to( be_less_or_equal( 3 ) ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: 3 (number) was expected to be less or equal: 3 (number) +FAILURE + Actual: 4 (number) was expected to be less or equal: 3 (number) + at "anonymous block", line 3 +FAILURE + Actual: 3 (number) was expected not to be less or equal: 3 (number) + at "anonymous block", line 4 +SUCCESS + Actual: 4 (number) was expected not to be less or equal: 3 (number) ``` ## be_less_than @@ -252,30 +532,64 @@ Usage: ```sql begin ut.expect( 3 ).to_be_less_than( 2 ); - --or - ut.expect( 3 ).to_( be_less_than( 2 ) ); + ut.expect( 0 ).to_( be_less_than( 2 ) ); + ut.expect( 3 ).not_to_be_less_than( 2 ); + ut.expect( 0 ).not_to( be_less_than( 2 ) ); end; +/ ``` +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: 3 (number) was expected to be less than: 2 (number) + at "anonymous block", line 2 +SUCCESS + Actual: 0 (number) was expected to be less than: 2 (number) +SUCCESS + Actual: 3 (number) was expected not to be less than: 2 (number) +FAILURE + Actual: 0 (number) was expected not to be less than: 2 (number) + at "anonymous block", line 5 +``` ## 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`. + Usage: ```sql begin - ut.expect( 'Lorem_impsum' ).to_be_like( a_mask => '%rem#_%', a_escape_char => '#' ); - ut.expect( 'Lorem_impsum' ).to_be_like( '%rem#_%', '#' ); - --or - ut.expect( 'Lorem_impsum' ).to_( be_like( a_mask => '%rem#_%', a_escape_char => '#' ) ); - ut.expect( 'Lorem_impsum' ).to_( be_like( '%rem#_%', '#' ) ); + ut.expect( 'Lorem_impsum' ).to_be_like( '%rem%'); + ut.expect( 'Lorem_impsum' ).to_be_like( '%rem\_i%', '\' ); + ut.expect( 'Lorem_impsum' ).to_( be_like( 'Lor_m%' ) ); + ut.expect( 'Lorem_impsum' ).not_to_be_like( '%rem%'); + ut.expect( 'Lorem_impsum' ).not_to( be_like( '%reM%') ); end; +/ ``` -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`. - +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: 'Lorem_impsum' (varchar2) was expected to be like: '%rem%' +SUCCESS + Actual: 'Lorem_impsum' (varchar2) was expected to be like: '%rem\_i%' , escape '\' +SUCCESS + Actual: 'Lorem_impsum' (varchar2) was expected to be like: 'Lor_m%' +FAILURE + Actual: 'Lorem_impsum' (varchar2) was expected not to be like: '%rem%' + at "anonymous block", line 5 +SUCCESS + Actual: 'Lorem_impsum' (varchar2) was expected not to be like: '%reM%' +``` ## be_not_null Unary matcher that validates if the actual value is not null. @@ -284,11 +598,25 @@ Usage: ```sql begin ut.expect( to_clob('ABC') ).to_be_not_null(); - --or - ut.expect( to_clob('ABC') ).to_( be_not_null() ); - --or - ut.expect( to_clob('ABC') ).not_to( be_null() ); + ut.expect( to_clob('') ).to_( be_not_null() ); + ut.expect( to_clob('ABC') ).not_to_be_not_null(); + ut.expect( '').not_to( be_not_null() ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: 'ABC' (clob) was expected to be not null +FAILURE + Actual: NULL (clob) was expected to be not null + at "anonymous block", line 3 +FAILURE + Actual: 'ABC' (clob) was expected not to be not null + at "anonymous block", line 4 +SUCCESS + Actual: NULL (varchar2) was expected not to be not null ``` ## be_null @@ -297,23 +625,54 @@ Unary matcher that validates if the actual value is null. Usage: ```sql begin - ut.expect( cast(null as varchar2(100)) ).to_be_null(); - --or - ut.expect( cast(null as varchar2(100)) ).to_( be_null() ); + ut.expect( '' ).to_be_null(); + ut.expect( 0 ).to_( be_null() ); + ut.expect( '' ).not_to_be_null(); + ut.expect( 0 ).not_to( be_null() ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: NULL (varchar2) was expected to be null +FAILURE + Actual: 0 (number) was expected to be null + at "anonymous block", line 3 +FAILURE + Actual: NULL (varchar2) was expected not to be null + at "anonymous block", line 4 +SUCCESS + Actual: 0 (number) was expected not to be null ``` ## be_true Unary matcher that validates if the provided value is true. -- `boolean` Usage: ```sql -begin - ut.expect( ( 1 = 1 ) ).to_be_true(); - --or +begin + ut.expect( ( 1 = 0 ) ).to_be_true(); ut.expect( ( 1 = 1 ) ).to_( be_true() ); + ut.expect( ( 1 = 0 ) ).not_to_be_true(); + ut.expect( ( 1 = 1 ) ).not_to( be_true() ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: FALSE (boolean) was expected to be true + at "anonymous block", line 2 +SUCCESS + Actual: TRUE (boolean) was expected to be true +SUCCESS + Actual: FALSE (boolean) was expected not to be true +FAILURE + Actual: TRUE (boolean) was expected not to be true + at "anonymous block", line 5 ``` ## have_count @@ -323,123 +682,158 @@ Can be used with `refcursor` , `json`or `table type` Usage: ```sql -procedure test_if_cursor_is_empty is +declare l_cursor sys_refcursor; + l_collection ut_varchar2_list; begin open l_cursor for select * from dual connect by level <=10; ut.expect( l_cursor ).to_have_count(10); - --or + open l_cursor for select rownum from xmltable('1 to 5'); ut.expect( l_cursor ).to_( have_count(10) ); + l_collection := ut_varchar2_list( 'a', 'a', 'b' ); + ut.expect( anydata.convertCollection( l_collection ) ).not_to_have_count(10); + ut.expect( anydata.convertCollection( l_collection ) ).not_to( have_count(3) ); end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: (refcursor [ count = 10 ]) was expected to have [ count = 10 ] +FAILURE + Actual: (refcursor [ count = 5 ]) was expected to have [ count = 10 ] + at "anonymous block", line 8 +SUCCESS + Actual: ut3.ut_varchar2_list [ count = 3 ] was expected not to have [ count = 10 ] +FAILURE + Actual: ut3.ut_varchar2_list [ count = 3 ] was expected not to have [ count = 3 ] + at "anonymous block", line 11 ``` ## 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) + Usage: ```sql begin - ut.expect( a_actual => '123-456-ABcd' ).to_match( a_pattern => '\d{3}-\d{3}-[a-z]', a_modifiers => 'i' ); - ut.expect( 'some value' ).to_match( '^some.*' ); - --or - ut.expect( a_actual => '123-456-ABcd' ).to_( match( a_pattern => '\d{3}-\d{3}-[a-z]', a_modifiers => 'i' ) ); - ut.expect( 'some value' ).to_( match( '^some.*' ) ); + ut.expect( '123-456-ABcd' ).to_match( '\d{3}-\d{3}-[a-z]{4}', 'i' ); + ut.expect( 'some value' ).to_( match( '^some.*' ) ) ; + ut.expect( '123-456-ABcd' ).not_to_match( '\d{3}-\d{3}-[a-z]{4}', 'i' ); + ut.expect( 'some value' ).not_to( match( '^some.*' ) ) ; end; +/ ``` -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) +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: '123-456-ABcd' (varchar2) was expected to match: '\d{3}-\d{3}-[a-z]{4}' , modifiers 'i' +SUCCESS + Actual: 'some value' (varchar2) was expected to match: '^some.*' +FAILURE + Actual: '123-456-ABcd' (varchar2) was expected not to match: '\d{3}-\d{3}-[a-z]{4}' , modifiers 'i' + at "anonymous block", line 4 +FAILURE + Actual: 'some value' (varchar2) was expected not to match: '^some.*' + at "anonymous block", line 5 +``` ## 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 `varchar2` to a `number` 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. -The matcher enables detection data-type changes. + +The matcher enables detection of data-type changes. If you expect your variable to be a number and it is now some other type, the test will fail and give you early indication of a potential problem. To keep it simple, the `equal` matcher will only succeed if you compare apples to apples. +Syntax: + +`ut.expect( a_actual ).to_equal( a_expected [, a_nulls_are_equal])[.advanced_options]` Example usage ```sql -function get_animal return varchar2 is +declare + l_actual varchar2(20); + l_expected varchar2(20); begin - return 'a dog'; + --Arrange + l_actual := 'a dog'; + --Assert + ut.expect( l_actual ).to_equal( 'other_dog' ); + ut.expect( l_actual ).to_equal( '' ); + ut.expect( l_actual ).to_equal( 1 ); + + l_actual := null; + ut.expect( l_actual ).to_equal( '' ); + ut.expect( l_actual ).to_equal( '', a_nulls_are_equal => false ); + ut.expect( l_actual ).not_to_equal( '' ); + ut.expect( sysdate ).to_equal( sysdate ); + ut.expect( sysdate ).to_equal( current_timestamp ); + ut.expect( current_timestamp ).to_equal( systimestamp ); + ut.expect( to_clob('varchar') ).to_equal( 'varchar' ); + ut.expect( to_blob('aa') ).to_equal( to_blob('aa') ); + ut.expect( to_clob('aa') ).to_equal( to_clob('aa') ); + ut.expect( to_blob('aa') ).to_equal( to_clob('aa') ); end; / +``` -create or replace package test_animals_getter is - - --%suite(Animals getter tests) - - --%test(get_animal - returns a dog) - procedure test_variant_1_get_animal; - --%test(get_animal - returns a dog) - procedure test_variant_2_get_animal; - --%test(get_animal - returns a dog) - procedure test_variant_3_get_animal; - --%test(get_animal - returns a dog) - procedure test_variant_4_get_animal; - --%test(get_animal - returns a dog) - procedure test_variant_5_get_animal; -end; -/ -create or replace package body test_animals_getter is - - --The below tests perform exactly the same check. - --They use different syntax to achieve the goal. - procedure test_variant_1_get_animal is - l_actual varchar2(100) := 'a dog'; - l_expected varchar2(100); - begin - --Arrange - l_actual := 'a dog'; - --Act - l_expected := get_animal(); - --Assert - ut.expect( l_actual ).to_equal( l_expected ); - end; - - procedure test_variant_2_get_animal is - l_expected varchar2(100); - begin - --Act - l_expected := get_animal(); - --Assert - ut.expect( l_expected ).to_equal( 'a dog' ); - end; - - procedure test_variant_3_get_animal is - begin - --Act / Assert - ut.expect( get_animal() ).to_equal( 'a dog' ); - end; - - procedure test_variant_4_get_animal is - begin - --Act / Assert - ut.expect( get_animal() ).to_equal( 'a dog', a_nulls_are_equal => true ); - end; - - procedure test_variant_5_get_animal is - begin - --Act / Assert - ut.expect( get_animal() ).to_( equal( 'a dog' ) ); - end; - - procedure test_variant_6_get_animal is - begin - --Act / Assert - ut.expect( get_animal() ).to_( equal( 'a dog', a_nulls_are_equal => true ) ); - end; -end; +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: 'a dog' (varchar2) was expected to equal: 'other_dog' (varchar2) + at "anonymous block", line 8 +FAILURE + Actual: 'a dog' (varchar2) was expected to equal: NULL (varchar2) + at "anonymous block", line 9 +FAILURE + Actual (varchar2) cannot be compared to Expected (number) using matcher 'equal'. + at "anonymous block", line 10 +SUCCESS + Actual: NULL (varchar2) was expected to equal: NULL (varchar2) +FAILURE + Actual: NULL (varchar2) was expected to equal: NULL (varchar2) + at "anonymous block", line 14 +FAILURE + Actual: NULL (varchar2) was expected not to equal: NULL (varchar2) + at "anonymous block", line 15 +SUCCESS + Actual: 2019-07-07T22:50:21 (date) was expected to equal: 2019-07-07T22:50:21 (date) +FAILURE + Actual (date) cannot be compared to Expected (timestamp with time zone) using matcher 'equal'. + at "anonymous block", line 17 +FAILURE + Actual: 2019-07-07T23:50:21.159268000 +01:00 (timestamp with time zone) was expected to equal: 2019-07-07T22:50:21.159296000 +00:00 (timestamp with time zone) + at "anonymous block", line 18 +FAILURE + Actual (clob) cannot be compared to Expected (varchar2) using matcher 'equal'. + at "anonymous block", line 19 +SUCCESS + Actual: 'AA' (blob) was expected to equal: 'AA' (blob) +SUCCESS + Actual: 'aa' (clob) was expected to equal: 'aa' (clob) +FAILURE + Actual (blob) cannot be compared to Expected (clob) using matcher 'equal'. + at "anonymous block", line 22 ``` -**Comparing NULLs is by default a success!** + +**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. ## contain -This matcher supports only compound data comparison. It check if the give set contain all values from given subset. +This matcher supports only compound data-types comparison. It check if the actual set contains all values of expected subset. When comparing data using `contain` matcher, the data-types of columns for compared compound types must be exactly the same. @@ -451,263 +845,180 @@ The matcher will cause a test to fail if actual data set does not contain any of ![included_set](../images/venn21.gif) -*Example 1*. - +**Example 1.** ```sql - procedure ut_refcursors is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - begin - --Arrange - open l_actual for select rownum as rn from dual a connect by level < 10; - open l_expected for select rownum as rn from dual a connect by level < 4 - union all select rownum as rn from dual a connect by level < 4; - - --Act - ut.expect(l_actual).to_contain(l_expected); - end; +declare + l_actual sys_refcursor; + l_expected sys_refcursor; +begin + --Arrange + open l_actual for select rownum as rn from dual a connect by level < 10; + open l_expected for select rownum as rn from dual a connect by level < 4 + union all select rownum as rn from dual a connect by level < 4; + + --Act + ut.expect(l_actual).to_contain(l_expected); +end; +/ ``` -Will result in failure message - -```sql - 1) ut_refcursors - Actual: refcursor [ count = 9 ] was expected to contain: refcursor [ count = 6 ] - Diff: - Rows: [ 3 differences ] - Missing: 3 - Missing: 2 - Missing: 1 +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: refcursor [ count = 9 ] was expected to contain: refcursor [ count = 6 ] + Diff: + Rows: [ 3 differences ] + Missing: 1 + Missing: 2 + Missing: 3 + at "anonymous block", line 11 ``` - -When duplicate rows are present in expected data set, actual data set must also include the same amount of duplicates. - -*Example 2.* +When duplicate rows are present in expected data set, actual data set must also include the same amount of duplicates. +**Example 2.** ```sql -create or replace package ut_duplicate_test is - - --%suite(Sample Test Suite) - - --%test(Ref Cursor contain duplicates) - procedure ut_duplicate_contain; - -end ut_duplicate_test; +declare + l_actual ut_varchar2_list; + l_expected ut_varchar2_list; +begin + l_actual := ut_varchar2_list( 1, 2, 3, 4, 5, 6, 7, 8, 1 ); + l_expected := ut_varchar2_list( 1, 2, 1, 2 ); + ut.expect( anydata.convertCollection( l_actual ) ).to_contain( anydata.convertCollection( l_expected ) ); +end; / - -create or replace package body ut_duplicate_test is - procedure ut_duplicate_contain is - l_actual sys_refcursor; - l_expected sys_refcursor; - begin - open l_expected for select mod(level,2) as rn from dual connect by level < 5; - open l_actual for select mod(level,8) as rn from dual connect by level < 9; - ut.expect(l_actual).to_contain(l_expected); - end; - -end ut_duplicate_test; ``` -Will result in failure test message - -```sql - 1) ut_duplicate_contain - Actual: refcursor [ count = 8 ] was expected to contain: refcursor [ count = 4 ] - Diff: - Rows: [ 2 differences ] - Missing: 0 - Missing: 1 +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: ut3.ut_varchar2_list [ count = 9 ] was expected to contain: ut3.ut_varchar2_list [ count = 4 ] + Diff: + Rows: [ 1 differences ] + Missing: 2 + at "anonymous block", line 7 ``` - - The negated version of `contain` ( `not_to_contain` ) is successful only when all values from expected set are not part of actual (they are disjoint and there is no overlap). - ![not_overlapping_set](../images/venn22.gif) -*Example 3.* - -Set 1 is defined as [ A , B , C ] - -*Set 2 is defined as [A , D , E ]* - -*Result : This will fail both of options to `to_contain` and `not_to_contain`* - - - -*Example 4.* - -Set 1 is defined as [ A , B , C , D ] - -*Set 2 is defined as [A , B , D ]* - -*Result : This will be success on option `to_contain` and fail `not_to_contain`* - - - -*Example 5. - -Set 1 is defined as [ A , B , C ] - -*Set 2 is defined as [D, E , F ]* - -*Result : This will be success on options `not_to_contain` and fail `to_contain`* - - - -Example usage - +**Example 3.** ```sql -create or replace package example_contain is - --%suite(Contain test) - - --%test( Cursor contains data from another cursor) - procedure cursor_to_contain; - - --%test( Cursor contains data from another cursor) - procedure cursor_not_to_contain; - - --%test( Cursor fail on to_contain) - procedure cursor_fail_contain; - - --%test( Cursor fail not_to_contain) - procedure cursor_fail_not_contain; +declare + l_actual ut_varchar2_list; + l_expected ut_varchar2_list; +begin + l_actual := ut_varchar2_list( 'A', 'B', 'C' ); + l_expected := ut_varchar2_list( 'A', 'B', 'E' ); + ut.expect( anydata.convertCollection( l_actual ) ).to_contain( anydata.convertCollection( l_expected ) ); + ut.expect( anydata.convertCollection( l_actual ) ).not_to_contain( anydata.convertCollection( l_expected ) ); end; / - -create or replace package body example_contain is - - procedure cursor_to_contain is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - begin - --Arrange - open l_actual for - select 'a' as name from dual union all - select 'b' as name from dual union all - select 'c' as name from dual union all - select 'd' as name from dual; - - open l_expected for - select 'a' as name from dual union all - select 'b' as name from dual union all - select 'c' as name from dual; - - --Act - ut.expect(l_actual).to_contain(l_expected); - end; - - procedure cursor_not_to_contain is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - begin - --Arrange - open l_actual for - select 'a' as name from dual union all - select 'b' as name from dual union all - select 'c' as name from dual; - - open l_expected for - select 'd' as name from dual union all - select 'e' as name from dual union all - select 'f' as name from dual; - - --Act - ut.expect(l_actual).not_to_contain(l_expected); - end; - - procedure cursor_fail_contain is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - begin - --Arrange - open l_actual for - select 'a' as name from dual union all - select 'b' as name from dual union all - select 'c' as name from dual; - - open l_expected for - select 'a' as name from dual union all - select 'd' as name from dual union all - select 'e' as name from dual; +``` - --Act - ut.expect(l_actual).to_contain(l_expected); - end; +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: ut3_latest_release.ut_varchar2_list [ count = 3 ] was expected to contain: ut3_latest_release.ut_varchar2_list [ count = 3 ] + Diff: + Rows: [ 1 differences ] + Missing: E + at "anonymous block", line 7 +FAILURE + Actual: (ut3_latest_release.ut_varchar2_list [ count = 3 ]) + Data-types: + VARCHAR2 + Data: + ABC + was expected not to contain:(ut3_latest_release.ut_varchar2_list [ count = 3 ]) + Data-types: + VARCHAR2 + Data: + ABE + at "anonymous block", line 8 +``` - procedure cursor_fail_not_contain is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - begin - --Arrange - open l_actual for - select 'a' as name from dual union all - select 'b' as name from dual union all - select 'c' as name from dual; - - open l_expected for - select 'a' as name from dual union all - select 'd' as name from dual union all - select 'e' as name from dual; +**Example 4.** - --Act - ut.expect(l_actual).not_to_contain(l_expected); - end; +```sql +declare + l_actual ut_varchar2_list; + l_expected ut_varchar2_list; +begin + l_actual := ut_varchar2_list( 'A', 'B', 'C', 'D' ); + l_expected := ut_varchar2_list( 'A', 'B', 'D' ); + ut.expect( anydata.convertCollection( l_actual ) ).to_contain( anydata.convertCollection( l_expected ) ); + ut.expect( anydata.convertCollection( l_actual ) ).not_to_contain( anydata.convertCollection( l_expected ) ); end; / ``` +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: ut3_latest_release.ut_varchar2_list [ count = 4 ] was expected to contain: ut3_latest_release.ut_varchar2_list [ count = 3 ] +FAILURE + Actual: (ut3_latest_release.ut_varchar2_list [ count = 4 ]) + Data-types: + VARCHAR2 + Data: + ABCD + was expected not to contain:(ut3_latest_release.ut_varchar2_list [ count = 3 ]) + Data-types: + VARCHAR2 + Data: + ABD + at "anonymous block", line 8 +``` - -Above execution will provide results as follow: -Cursor contains data from another cursor -Cursor contains data from another cursor -Cursor fail on to_contain -Cursor fail not_to_contain +**Example 5.** ```sql -Contain test - Cursor contains data from another cursor [.045 sec] - Cursor contains data from another cursor [.039 sec] - Cursor fail on to_contain [.046 sec] (FAILED - 1) - Cursor fail not_to_contain [.043 sec] (FAILED - 2) - -Failures: - - 1) cursor_fail_contain - Actual: refcursor [ count = 3 ] was expected to contain: refcursor [ count = 3 ] - Diff: - Rows: [ 2 differences ] - Missing: d - Missing: e - at "UT3.EXAMPLE_CONTAIN.CURSOR_FAIL_CONTAIN", line 71 ut.expect(l_actual).to_contain(l_expected); - - - 2) cursor_fail_not_contain - Actual: (refcursor [ count = 3 ]) - Data-types: - CHAR - - Data: - a - b - c - was expected not to contain:(refcursor [ count = 3 ]) - Data-types: - CHAR - - Data: - a - d - e - at "UT3.EXAMPLE_CONTAIN.CURSOR_FAIL_NOT_CONTAIN", line 94 ut.expect(l_actual).not_to_contain(l_expected); +declare + l_actual ut_varchar2_list; + l_expected ut_varchar2_list; +begin + l_actual := ut_varchar2_list( 'A', 'B', 'C' ); + l_expected := ut_varchar2_list( 'D', 'E', 'F' ); + ut.expect( anydata.convertCollection( l_actual ) ).to_contain( anydata.convertCollection( l_expected ) ); + ut.expect( anydata.convertCollection( l_actual ) ).not_to_contain( anydata.convertCollection( l_expected ) ); +end; +/ +``` + +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: ut3_latest_release.ut_varchar2_list [ count = 3 ] was expected to contain: ut3_latest_release.ut_varchar2_list [ count = 3 ] + Diff: + Rows: [ 3 differences ] + Missing: D + Missing: E + Missing: F + at "anonymous block", line 7 +SUCCESS + Actual: (ut3_latest_release.ut_varchar2_list [ count = 3 ]) + Data-types: + VARCHAR2 + Data: + ABC + was expected not to contain:(ut3_latest_release.ut_varchar2_list [ count = 3 ]) + Data-types: + VARCHAR2 + Data: + DEF ``` +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- + TODO - continue doc rewrite + TODO - fix negated results for contain & equal +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- ## Comparing cursors, object types, nested tables and varrays @@ -1286,7 +1597,7 @@ create or replace package body test_expectations_json is ] }'); - ut3.ut.expect( l_actual ).to_equal( l_actual ); + ut.expect( l_actual ).to_equal( l_actual ); end; end; @@ -1374,69 +1685,9 @@ create or replace package body test_expectations_json is l_array_actual := json_array_t(json_query(l_actual.stringify,'$.Actors.children')); l_array_expected := json_array_t(json_query(l_expected.stringify,'$.Actors[1].children')); --Act - ut3.ut.expect(l_array_actual).to_equal(l_array_expected); + ut.expect(l_array_actual).to_equal(l_array_expected); end; end; / ``` - - - - - - - -# Negating a matcher - -Expectations provide a very convenient way to perform a check on a negated matcher. - -Syntax to check for matcher evaluating to true: -```sql -begin - ut.expect( a_actual {data-type} ).to_{matcher}; - ut.expect( a_actual {data-type} ).to_( {matcher} ); -end; -``` - -Syntax to check for matcher evaluating to false: -```sql -begin - ut.expect( a_actual {data-type} ).not_to_{matcher}; - ut.expect( a_actual {data-type} ).not_to( {matcher} ); -end; -``` - -If a matcher evaluated to NULL, then both `to_` and `not_to` will cause the expectation to report failure. - -Example: -```sql -begin - ut.expect( null ).to_( be_true() ); - ut.expect( null ).not_to( be_true() ); -end; -``` -Since NULL is neither *true* nor *false*, both expectations will report failure. - -# Supported data types - -The matrix below illustrates the data types supported by different matchers. - -| Matcher | blob | boolean | clob | date | number | timestamp | timestamp
with
timezone | timestamp
with
local
timezone | varchar2 | interval
year
to
month | interval
day
to
second | cursor | nested
table
/ varray | object | json | -| :---------------------: | :--: | :-----: | :--: | :--: | :----: | :-------: | :---------------------------: | :------------------------------------: | :------: | :-----------------------------: | :-----------------------------: | :----: | :-------------------------: | :----: | :--: | -| **be_not_null** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | -| **be_null** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | -| **be_false** | | X | | | | | | | | | | | | | | -| **be_true** | | X | | | | | | | | | | | | | | -| **be_greater_than** | | | | X | X | X | X | X | | X | X | | | | | -| **be_greater_or_equal** | | | | X | X | X | X | X | | X | X | | | | | -| **be_less_or_equal** | | | | X | X | X | X | X | | X | X | | | | | -| **be_less_than** | | | | X | X | X | X | X | | X | X | | | | | -| **be_between** | | | | X | X | X | X | X | X | X | X | | | | | -| **equal** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | -| **contain** | | | | | | | | | | | | X | X | X | | -| **match** | | | X | | | | | | X | | | | | | | -| **be_like** | | | X | | | | | | X | | | | | | | -| **be_empty** | X | | X | | | | | | | | | X | X | | X | -| **have_count** | | | | | | | | | | | | X | X | | X | - diff --git a/source/core/types/ut_expectation_result.tpb b/source/core/types/ut_expectation_result.tpb index 097c900f4..9aa8b81c0 100644 --- a/source/core/types/ut_expectation_result.tpb +++ b/source/core/types/ut_expectation_result.tpb @@ -24,7 +24,7 @@ create or replace type body ut_expectation_result is self.self_type := $$plsql_unit; self.status := a_status; self.description := a_description; - self.message := a_message; + self.message := a_message; if self.status = ut_utils.gc_failure and a_include_caller_info then self.caller_info := ut_expectation_processor.who_called_expectation(dbms_utility.format_call_stack()); end if; diff --git a/source/core/ut_expectation_processor.pkb b/source/core/ut_expectation_processor.pkb index 4d773f833..0c2580220 100644 --- a/source/core/ut_expectation_processor.pkb +++ b/source/core/ut_expectation_processor.pkb @@ -90,6 +90,9 @@ create or replace package body ut_expectation_processor as for i in 1 .. l_results.count loop dbms_output.put_line( ' ' || l_results(i) ); end loop; + if a_expectation_result.caller_info is not null then + dbms_output.put_line( ut_utils.indent_lines( a_expectation_result.caller_info, 2, true) ); + end if; end if; end; @@ -147,28 +150,55 @@ create or replace package body ut_expectation_processor as function who_called_expectation(a_call_stack varchar2) return varchar2 is l_caller_stack_line varchar2(4000); + l_call_stack varchar2(4000); l_line_no integer; l_owner varchar2(1000); l_object_name varchar2(1000); - l_object_full_name varchar2(1000); l_result varchar2(4000); -- in 12.2 format_call_stack reportes not only package name, but also the procedure name -- when 11g and 12c reports only package name - c_expectation_search_pattern constant varchar2(500) := - '(.*\.(UT_EXPECTATION[A-Z0-9#_$]*|UT|UTASSERT2?)(\.[A-Z0-9#_$]+)?\s+)+(.*)'; + function cut_header_and_expectations( a_stack varchar2 ) return varchar2 is + begin + return regexp_substr( a_stack, '(.*\.(UT_EXPECTATION[A-Z0-9#_$]*|UT|UTASSERT2?)(\.[A-Z0-9#_$]+)?\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, 'm' ); + 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.*', + '', + 1, 1, 'mni' + ); + end; + function format_stack( a_stack varchar2 ) return varchar2 is + begin + return regexp_replace( + a_stack, + '([0-9]+)\s+(.* )?((anonymous block)|(([0-9_$#a-z]+\.[0-9_$#a-z]+(\.([0-9_$#a-z])+)?)))', + 'at "\3", line \1', 1, 0, 'i' + ); + end; begin - l_caller_stack_line := regexp_substr( a_call_stack, c_expectation_search_pattern, 1, 1, 'm', 4); + l_call_stack := cut_header_and_expectations( a_call_stack ); + l_call_stack := cut_address_columns( l_call_stack ); + l_call_stack := cut_framework_stack( l_call_stack ); + l_call_stack := format_stack( l_call_stack ); + 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,'(0x)?[0-9a-f]+\s+(\d+)',subexpression => 2) ); - l_owner := regexp_substr(l_caller_stack_line,'([A-Za-z0-9$#_]+)\.([A-Za-z0-9$#_]|\.)+',subexpression => 1); - l_object_name := regexp_substr(l_caller_stack_line,'([A-Za-z0-9$#_]+)\.([A-Za-z0-9$#_]+)',subexpression => 2); - l_object_full_name := regexp_substr(l_caller_stack_line,'([A-Za-z0-9$#_]+)\.(([A-Za-z0-9$#_]|\.)+)',subexpression => 2); - if l_owner is not null and l_object_name is not null and l_line_no is not null then - l_result := 'at "' || l_owner || '.' || l_object_full_name || '", line '|| l_line_no || ' ' - || ut_metadata.get_source_definition_line(l_owner, l_object_name, l_line_no); - end if; + 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_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 ); + else + l_result := l_call_stack; end if; - return l_result; + return rtrim(l_result,chr(10)); end; procedure add_warning(a_messsage varchar2) is diff --git a/source/create_grants.sql b/source/create_grants.sql index 96fcb7cfa..4553c52f6 100644 --- a/source/create_grants.sql +++ b/source/create_grants.sql @@ -75,6 +75,7 @@ grant execute on &&ut3_owner..ut_expectation_compound to &ut3_user; grant execute on &&ut3_owner..ut_expectation_json to &ut3_user; --matchers +grant execute on &&ut3_owner..ut_matcher to &ut3_user; grant execute on &&ut3_owner..ut_be_between to &ut3_user; grant execute on &&ut3_owner..ut_be_empty to &ut3_user; grant execute on &&ut3_owner..ut_be_false to &ut3_user; diff --git a/source/create_synonyms.sql b/source/create_synonyms.sql index 39d559b20..36dc465de 100644 --- a/source/create_synonyms.sql +++ b/source/create_synonyms.sql @@ -91,6 +91,7 @@ create &action_type. synonym &ut3_user.ut_expectation_compound for &&ut3_owner.. create &action_type. synonym &ut3_user.ut_expectation_json for &&ut3_owner..ut_expectation_json; --matchers +create &action_type. synonym &ut3_user.ut_matcher for &&ut3_owner..ut_matcher; create &action_type. synonym &ut3_user.be_between for &&ut3_owner..be_between; create &action_type. synonym &ut3_user.be_empty for &&ut3_owner..be_empty; create &action_type. synonym &ut3_user.be_false for &&ut3_owner..be_false; diff --git a/source/expectations/data_values/ut_compound_data_helper.pkb b/source/expectations/data_values/ut_compound_data_helper.pkb index 51fa61fbd..575568641 100644 --- a/source/expectations/data_values/ut_compound_data_helper.pkb +++ b/source/expectations/data_values/ut_compound_data_helper.pkb @@ -608,7 +608,7 @@ create or replace package body ut_compound_data_helper is begin return 'SQL exception thrown when fetching data from cursor:'|| ut_utils.remove_error_from_stack(sqlerrm,ut_utils.gc_xml_processing)||chr(10)|| - ut_expectation_processor.who_called_expectation(a_error_stack)|| + ut_expectation_processor.who_called_expectation(a_error_stack)||chr(10)|| 'Check the query and data for errors.'; end; diff --git a/source/expectations/data_values/ut_compound_data_value.tpb b/source/expectations/data_values/ut_compound_data_value.tpb index 5874beef8..627e14680 100644 --- a/source/expectations/data_values/ut_compound_data_value.tpb +++ b/source/expectations/data_values/ut_compound_data_value.tpb @@ -16,9 +16,14 @@ create or replace type body ut_compound_data_value as limitations under the License. */ + member function get_elements_count_info return varchar2 is + begin + return case when elements_count is null then ' [ null ]' else ' [ count = '||elements_count||' ]' end; + end; + overriding member function get_object_info return varchar2 is begin - return self.data_type||' [ count = '||self.elements_count||' ]'; + return self.data_type||get_elements_count_info(); end; overriding member function is_null return boolean is @@ -37,7 +42,6 @@ create or replace type body ut_compound_data_value as end; overriding member function to_string return varchar2 is - l_results ut_utils.t_clob_tab; l_result clob; l_result_string varchar2(32767); begin diff --git a/source/expectations/data_values/ut_compound_data_value.tps b/source/expectations/data_values/ut_compound_data_value.tps index a9b78f944..027b5ca26 100644 --- a/source/expectations/data_values/ut_compound_data_value.tps +++ b/source/expectations/data_values/ut_compound_data_value.tps @@ -39,7 +39,8 @@ create or replace type ut_compound_data_value force under ut_data_value( * Holds name for the type of compound */ compound_type varchar2(50), - + + member function get_elements_count_info return varchar2, overriding member function get_object_info return varchar2, overriding member function is_null return boolean, overriding member function is_diffable return boolean, diff --git a/source/expectations/data_values/ut_data_value.tpb b/source/expectations/data_values/ut_data_value.tpb index d8feb1a62..a644fe30e 100644 --- a/source/expectations/data_values/ut_data_value.tpb +++ b/source/expectations/data_values/ut_data_value.tpb @@ -59,7 +59,7 @@ create or replace type body ut_data_value as l_result := l_result || chr(10); end if; else - l_result := self.to_string() || ' ' || l_info || ' '; + l_result := self.to_string() || ' ' || l_info; end if; return l_result; end; diff --git a/source/expectations/data_values/ut_data_value_anydata.tpb b/source/expectations/data_values/ut_data_value_anydata.tpb index 0b5447758..a81b0f5c7 100644 --- a/source/expectations/data_values/ut_data_value_anydata.tpb +++ b/source/expectations/data_values/ut_data_value_anydata.tpb @@ -18,7 +18,7 @@ create or replace type body ut_data_value_anydata as overriding member function get_object_info return varchar2 is begin - return self.data_type || case when self.compound_type = 'collection' then ' [ count = '||self.elements_count||' ]' else null end; + return self.data_type || case when self.compound_type = 'collection' then self.get_elements_count_info() end; end; member function get_extract_path(a_data_value anydata) return varchar2 is diff --git a/source/expectations/data_values/ut_data_value_json.tpb b/source/expectations/data_values/ut_data_value_json.tpb index e1ef4d7e2..83916c30e 100644 --- a/source/expectations/data_values/ut_data_value_json.tpb +++ b/source/expectations/data_values/ut_data_value_json.tpb @@ -113,9 +113,10 @@ create or replace type body ut_data_value_json as ut_utils.append_to_clob(l_result, l_results); end if; - - - l_result_string := ut_utils.to_string(l_result,null); + + if l_result != empty_clob() then + l_result_string := chr(10) || 'Diff:' || ut_utils.to_string(l_result,null); + end if; dbms_lob.freetemporary(l_result); return l_result_string; end; diff --git a/source/expectations/data_values/ut_data_value_refcursor.tpb b/source/expectations/data_values/ut_data_value_refcursor.tpb index dab76a817..801b35802 100644 --- a/source/expectations/data_values/ut_data_value_refcursor.tpb +++ b/source/expectations/data_values/ut_data_value_refcursor.tpb @@ -220,7 +220,7 @@ create or replace type body ut_data_value_refcursor as a_match_options.ordered_columns() ); - if l_column_diffs.count > 0 then + if l_column_diffs is not empty then ut_utils.append_to_clob(l_result,chr(10) || 'Columns:' || chr(10)); l_other_cols := remove_incomparable_cols( l_other_cols, l_column_diffs ); l_self_cols := remove_incomparable_cols( l_self_cols, l_column_diffs ); @@ -267,8 +267,8 @@ create or replace type body ut_data_value_refcursor as l_results(l_results.last) := get_diff_message(l_row_diffs(i),a_match_options.unordered); end loop; ut_utils.append_to_clob(l_result,l_results); - else - l_message:= chr(10)||'Rows: [ all different ]'||chr(10)||' All rows are different as the columns position is not matching.'; + elsif l_column_diffs is not empty then + l_message:= chr(10)||'Rows: [ all different ]'||chr(10)||' All rows are different as the columns position is not matching.'; ut_utils.append_to_clob( l_result, l_message ); end if; else @@ -287,8 +287,9 @@ create or replace type body ut_data_value_refcursor as end if; end if; - - l_result_string := ut_utils.to_string(l_result,null); + if l_result != empty_clob() then + l_result_string := chr(10) || 'Diff:' || ut_utils.to_string(l_result,null); + end if; dbms_lob.freetemporary(l_result); return l_result_string; end; diff --git a/source/expectations/matchers/ut_contain.tpb b/source/expectations/matchers/ut_contain.tpb index cf6653f63..c0c4f481a 100644 --- a/source/expectations/matchers/ut_contain.tpb +++ b/source/expectations/matchers/ut_contain.tpb @@ -55,8 +55,7 @@ create or replace type body ut_contain as begin if self.expected.data_type = a_actual.data_type and self.expected.is_diffable then l_result := - 'Actual: '||a_actual.get_object_info()||' '||self.description()||': '||self.expected.get_object_info() - || chr(10) || 'Diff:' + 'Actual: '||a_actual.get_object_info()||self.description()||': '||self.expected.get_object_info() || treat(expected as ut_data_value_refcursor).diff( a_actual, self.options ); else l_result := (self as ut_matcher).failure_message(a_actual) || ': '|| self.expected.to_string_report(); diff --git a/source/expectations/matchers/ut_equal.tpb b/source/expectations/matchers/ut_equal.tpb index 2e774d396..afa2148f3 100644 --- a/source/expectations/matchers/ut_equal.tpb +++ b/source/expectations/matchers/ut_equal.tpb @@ -244,16 +244,15 @@ create or replace type body ut_equal as begin if self.expected.data_type = a_actual.data_type and self.expected.is_diffable then l_result := - 'Actual: '||a_actual.get_object_info()||' '||self.description()||': '||self.expected.get_object_info() - || chr(10) || 'Diff:' || - case - when self.expected is of (ut_data_value_refcursor) then - treat(expected as ut_data_value_refcursor).diff( a_actual, options ) - when self.expected is of (ut_data_value_json) then - treat(expected as ut_data_value_json).diff( a_actual, options ) - else - expected.diff( a_actual, options ) - end; + 'Actual: '||a_actual.get_object_info()||self.description()||': '||self.expected.get_object_info() + ||case + when self.expected is of (ut_data_value_refcursor) then + treat(expected as ut_data_value_refcursor).diff( a_actual, options ) + when self.expected is of (ut_data_value_json) then + treat(expected as ut_data_value_json).diff( a_actual, options ) + else + expected.diff( a_actual, options ) + end; else l_result := (self as ut_matcher).failure_message(a_actual) || ': '|| self.expected.to_string_report(); end if; diff --git a/source/expectations/matchers/ut_matcher.tpb b/source/expectations/matchers/ut_matcher.tpb index 228f2caad..11ff1b6b6 100644 --- a/source/expectations/matchers/ut_matcher.tpb +++ b/source/expectations/matchers/ut_matcher.tpb @@ -40,12 +40,12 @@ create or replace type body ut_matcher as member function description return varchar2 is begin - return 'was expected to '||name(); + return ' was expected to '||name(); end; member function description_when_negated return varchar2 is begin - return 'was expected not to '||name(); + return ' was expected not to '||name(); end; member function error_message(a_actual ut_data_value) return varchar2 is diff --git a/source/expectations/ut_expectation.tpb b/source/expectations/ut_expectation.tpb index b50efef6a..d8ed1f79a 100644 --- a/source/expectations/ut_expectation.tpb +++ b/source/expectations/ut_expectation.tpb @@ -81,6 +81,16 @@ create or replace type body ut_expectation as self.not_to( ut_be_false() ); end; + member procedure to_be_empty(self in ut_expectation) is + begin + self.to_( ut_be_empty() ); + end; + + member procedure not_to_be_empty(self in ut_expectation) is + begin + self.not_to( ut_be_empty() ); + end; + member procedure to_equal(self in ut_expectation, a_expected anydata, a_nulls_are_equal boolean := null) is begin self.to_( ut_equal(a_expected, a_nulls_are_equal) ); diff --git a/source/expectations/ut_expectation.tps b/source/expectations/ut_expectation.tps index 5b3370c43..37fefd085 100644 --- a/source/expectations/ut_expectation.tps +++ b/source/expectations/ut_expectation.tps @@ -33,6 +33,9 @@ create or replace type ut_expectation authid current_user as object( member procedure not_to_be_true(self in ut_expectation), member procedure not_to_be_false(self in ut_expectation), + member procedure to_be_empty(self in ut_expectation), + member procedure not_to_be_empty(self in ut_expectation), + -- this is done to provide strong type comparison. other comporators should be implemented in the type-specific classes member procedure to_equal(self in ut_expectation, a_expected anydata, a_nulls_are_equal boolean := null), member procedure to_equal(self in ut_expectation, a_expected anydata, a_exclude varchar2, a_nulls_are_equal boolean := null), diff --git a/source/expectations/ut_expectation_compound.tpb b/source/expectations/ut_expectation_compound.tpb index aaab7cb34..5e7268bd9 100644 --- a/source/expectations/ut_expectation_compound.tpb +++ b/source/expectations/ut_expectation_compound.tpb @@ -23,16 +23,6 @@ create or replace type body ut_expectation_compound as return; end; - member procedure to_be_empty(self in ut_expectation_compound) is - begin - self.to_( ut_be_empty() ); - end; - - member procedure not_to_be_empty(self in ut_expectation_compound) is - begin - self.not_to( ut_be_empty() ); - end; - member procedure to_have_count(self in ut_expectation_compound, a_expected integer) is begin self.to_( ut_have_count(a_expected) ); diff --git a/source/expectations/ut_expectation_compound.tps b/source/expectations/ut_expectation_compound.tps index bd17eb8b1..fc413e3fd 100644 --- a/source/expectations/ut_expectation_compound.tps +++ b/source/expectations/ut_expectation_compound.tps @@ -19,8 +19,6 @@ create or replace type ut_expectation_compound force under ut_expectation( constructor function ut_expectation_compound(self in out nocopy ut_expectation_compound, a_actual_data ut_data_value, a_description varchar2) return self as result, - member procedure to_be_empty(self in ut_expectation_compound), - member procedure not_to_be_empty(self in ut_expectation_compound), member procedure to_have_count(self in ut_expectation_compound, a_expected integer), member procedure not_to_have_count(self in ut_expectation_compound, a_expected integer), diff --git a/source/expectations/ut_expectation_json.tpb b/source/expectations/ut_expectation_json.tpb index 954daf4e7..49c0c2e1f 100644 --- a/source/expectations/ut_expectation_json.tpb +++ b/source/expectations/ut_expectation_json.tpb @@ -23,16 +23,6 @@ create or replace type body ut_expectation_json as return; end; - member procedure to_be_empty(self in ut_expectation_json) is - begin - self.to_( ut_be_empty() ); - end; - - member procedure not_to_be_empty(self in ut_expectation_json) is - begin - self.not_to( ut_be_empty() ); - end; - member function to_equal(a_expected json_element_t, a_nulls_are_equal boolean := null) return ut_expectation_json is l_result ut_expectation_json := self; begin diff --git a/source/expectations/ut_expectation_json.tps b/source/expectations/ut_expectation_json.tps index 9cc9aecb8..dd3aa5b25 100644 --- a/source/expectations/ut_expectation_json.tps +++ b/source/expectations/ut_expectation_json.tps @@ -19,8 +19,6 @@ create or replace type ut_expectation_json under ut_expectation( constructor function ut_expectation_json(self in out nocopy ut_expectation_json, a_actual_data ut_data_value, a_description varchar2) return self as result, - member procedure to_be_empty(self in ut_expectation_json), - member procedure not_to_be_empty(self in ut_expectation_json), member function to_equal(a_expected json_element_t , a_nulls_are_equal boolean := null) return ut_expectation_json, member function not_to_equal(a_expected json_element_t , a_nulls_are_equal boolean := null) return ut_expectation_json, member procedure to_have_count(self in ut_expectation_json, a_expected integer), diff --git a/test/ut3_tester/core/expectations/test_expectation_processor.pkb b/test/ut3_tester/core/expectations/test_expectation_processor.pkb index 17ffbec3c..ab413616e 100644 --- a/test/ut3_tester/core/expectations/test_expectation_processor.pkb +++ b/test/ut3_tester/core/expectations/test_expectation_processor.pkb @@ -1,5 +1,7 @@ create or replace package body test_expectation_processor is + gc_user constant varchar2(128) := sys_context('userenv','current_schema'); + procedure who_called_expectation is l_stack_trace varchar2(4000); l_source_line varchar2(4000); @@ -11,7 +13,9 @@ create or replace package body test_expectation_processor is 353dfeb2f8 26 SCH_TEST.UT_EXPECTATION_RESULT cba249ce0 112 SCH_TEST.UT_EXPECTATION 3539881cf0 21 SCH_TEST.UT_EXPECTATION_NUMBER -351a608008 28 package body SCH_TEST.TPKG_PRIOR_YEAR_GENERATION +351a608008 7 package body ]'||gc_user||q'[.TEST_EXPECTATION_PROCESSOR +351a608018 12 package body ]'||gc_user||q'[.TEST_EXPECTATION_PROCESSOR +351a608018 24 package body ]'||gc_user||q'[.TEST_EXPECTATION_PROCESSOR 351a6862b8 6 anonymous block 351fe31010 1825 package body SYS.DBMS_SQL 20befbe4d8 129 SCH_TEST.UT_EXECUTABLE @@ -30,7 +34,9 @@ cba24bfd0 75 SCH_TEST.UT_LOGICAL_SUITE ]'; ut.expect( ut3.ut_expectation_processor.who_called_expectation(l_stack_trace) - ).to_be_like('at "SCH_TEST.TPKG_PRIOR_YEAR_GENERATION", line 28 %'); + ).to_equal('at "'||gc_user||'.TEST_EXPECTATION_PROCESSOR", line 7 l_source_line varchar2(4000); +at "'||gc_user||'.TEST_EXPECTATION_PROCESSOR", line 12 +at "'||gc_user||'.TEST_EXPECTATION_PROCESSOR", line 24'); end; @@ -41,30 +47,24 @@ cba24bfd0 75 SCH_TEST.UT_LOGICAL_SUITE l_stack_trace := q'[----- PL/SQL Call Stack ----- object line object handle number name -34f88e4420 124 package body SCH_TEST.UT_EXPECTATION_PROCESSOR -353dfeb2f8 26 SCH_TEST.UT_EXPECTATION_RESULT -cba249ce0 112 SCH_TEST.UT_EXPECTATION -3539881cf0 21 SCH_TEST.UT_EXPECTATION_NUMBER -351a608008 28 package body SCH_TEST.TPKG_PRIOR_YEAR_GENERATION -351a6862b8 6 anonymous block -351fe31010 1825 package body SYS.DBMS_SQL -20befbe4d8 129 SCH_TEST.UT_EXECUTABLE -20befbe4d8 65 SCH_TEST.UT_EXECUTABLE -34f8ab7cd8 80 SCH_TEST.UT_TEST -34f8ab98f0 48 SCH_TEST.UT_SUITE_ITEM -34f8ab9b10 74 SCH_TEST.UT_SUITE -34f8ab98f0 48 SCH_TEST.UT_SUITE_ITEM -cba24bfd0 75 SCH_TEST.UT_LOGICAL_SUITE -353dfecf30 59 SCH_TEST.UT_RUN -34f8ab98f0 48 SCH_TEST.UT_SUITE_ITEM -357f5421e8 77 package body SCH_TEST.UT_RUNNER -357f5421e8 111 package body SCH_TEST.UT_RUNNER -20be951ab0 292 package body SCH_TEST.UT -20be951ab0 320 package body SCH_TEST.UT +0x80e701d8 26 UT3.UT_EXPECTATION_RESULT +0x85e10150 112 UT3.UT_EXPECTATION +0x8b54bad8 21 UT3.UT_EXPECTATION_NUMBER +0x85cfd238 20 package body UT3.UT_EXAMPLETEST +0x85def380 6 anonymous block +0x85e93750 1825 package body SYS.DBMS_SQL +0x80f4f608 129 UT3.UT_EXECUTABLE +0x80f4f608 65 UT3.UT_EXECUTABLE +0x8a116010 76 UT3.UT_TEST +0x8a3348a0 48 UT3.UT_SUITE_ITEM +0x887e9948 67 UT3.UT_LOGICAL_SUITE +0x8a26de20 59 UT3.UT_RUN +0x8a3348a0 48 UT3.UT_SUITE_ITEM +0x838d17c0 28 anonymous block ]'; ut.expect( ut3.ut_expectation_processor.who_called_expectation(l_stack_trace) - ).to_be_like('at "SCH_TEST.TPKG_PRIOR_YEAR_GENERATION", line 28 %'); + ).to_be_like('at "UT3.UT_EXAMPLETEST", line 20 %'); end; end; diff --git a/test/ut3_tester/core/expectations/test_expectation_processor.pks b/test/ut3_tester/core/expectations/test_expectation_processor.pks index 5b63c8938..73cf60d11 100644 --- a/test/ut3_tester/core/expectations/test_expectation_processor.pks +++ b/test/ut3_tester/core/expectations/test_expectation_processor.pks @@ -3,14 +3,17 @@ create or replace package test_expectation_processor is --%suite(expectation_processor) --%suitepath(utplsql.ut3_tester.core.expectations) - --%context(who_called_expectation) + --%beforeall(ut3_tester_helper.main_helper.set_ut_run_context) + --%afterall(ut3_tester_helper.main_helper.clear_ut_run_context) - --%test(parses stack trace and returns object and line that called expectation) - procedure who_called_expectation; + --%context(who_called_expectation_in_test) - --%test(parses stack trace containing 0x and returns object and line that called expectation) + --%test(parses stack trace containing 0x and returns objects and line that called expectation) procedure who_called_expectation_0x; + --%test(parses stack trace and returns objects and line that called expectation) + procedure who_called_expectation; + --%endcontext end; diff --git a/test/ut3_user/expectations.pkb b/test/ut3_user/expectations.pkb index c1f07dd9f..942da68f2 100644 --- a/test/ut3_user/expectations.pkb +++ b/test/ut3_user/expectations.pkb @@ -1,31 +1,64 @@ create or replace package body expectations as - --%test(Expectations return data to screen when called standalone) - procedure inline_expectation_to_dbms_out is l_expected sys_refcursor; l_actual sys_refcursor; l_output dbmsoutput_linesarray; l_results ut3.ut_varchar2_list; l_lines number := 10000; + pragma autonomous_transaction; begin --Arrange - ut3_tester_helper.main_helper.clear_ut_run_context; + --Act + execute immediate 'begin some_pkg.some_procedure; end;'; + ut3.ut.expect(1).to_equal(0); + ut3.ut.expect(0).to_equal(0); + + --Assert open l_expected for select 'FAILURE' as out_row from dual union all select 'Actual: 1 (number) was expected to equal: 0 (number)' from dual union all + select 'at "UT3$USER#.SOME_PKG.SOME_PROCEDURE", line 4 ut3.ut.expect(1).to_equal(0); + at "anonymous block", line 1 + at "UT3$USER#.EXPECTATIONS.INLINE_EXPECTATION_TO_DBMS_OUT", line 13' from dual union all + select 'SUCCESS' from dual union all + select 'Actual: 0 (number) was expected to equal: 0 (number)' from dual union all + select 'FAILURE' as out_row from dual union all + select 'Actual: 1 (number) was expected to equal: 0 (number)' from dual union all + select 'at "UT3$USER#.EXPECTATIONS.INLINE_EXPECTATION_TO_DBMS_OUT", line 14 ut3.ut.expect(1).to_equal(0);' from dual union all select 'SUCCESS' from dual union all select 'Actual: 0 (number) was expected to equal: 0 (number)' from dual union all select '' from dual; - --Act - ut3.ut.expect(1).to_equal(0); - ut3.ut.expect(0).to_equal(0); - - --Assert dbms_output.get_lines(lines => l_output, numlines => l_lines); open l_actual for select trim(column_value) as out_row from table(l_output); ut.expect(l_actual).to_equal(l_expected); + rollback; end; + + procedure create_some_pkg is + pragma autonomous_transaction; + begin + execute immediate q'[ + create or replace package some_pkg is + procedure some_procedure; + end;]'; + + execute immediate q'[ + create or replace package body some_pkg is + procedure some_procedure is + begin + ut3.ut.expect(1).to_equal(0); + ut3.ut.expect(0).to_equal(0); + end; + end;]'; + end; + + procedure drop_some_pkg is + pragma autonomous_transaction; + begin + execute immediate 'drop package some_pkg'; + end; + end; / \ No newline at end of file diff --git a/test/ut3_user/expectations.pks b/test/ut3_user/expectations.pks index ab8bd3835..0324f9ac0 100644 --- a/test/ut3_user/expectations.pks +++ b/test/ut3_user/expectations.pks @@ -7,8 +7,12 @@ create or replace package expectations as --%afterall(ut3_tester_helper.main_helper.clear_ut_run_context) --%test(Expectations return data to screen when called standalone) - --%aftertest(ut3_tester_helper.main_helper.set_ut_run_context) + --%beforetest( create_some_pkg, ut3_tester_helper.main_helper.clear_ut_run_context ) + --%aftertest( drop_some_pkg, ut3_tester_helper.main_helper.set_ut_run_context ) procedure inline_expectation_to_dbms_out; + procedure create_some_pkg; + procedure drop_some_pkg; + end; / \ No newline at end of file diff --git a/test/ut3_user/expectations/test_expectation_anydata.pkb b/test/ut3_user/expectations/test_expectation_anydata.pkb index 4838aabff..f94b0efc0 100644 --- a/test/ut3_user/expectations/test_expectation_anydata.pkb +++ b/test/ut3_user/expectations/test_expectation_anydata.pkb @@ -105,10 +105,7 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - l_expected_message := q'[%Actual: ut3_tester_helper.test_dummy_object_list [ count = ] was expected to equal: ut3_tester_helper.test_dummy_object_list [ count = 0 ] -%Diff: -%Rows: [ all different ] -%All rows are different as the columns position is not matching.]'; + l_expected_message := q'[%Actual: ut3_tester_helper.test_dummy_object_list [ null ] was expected to equal: ut3_tester_helper.test_dummy_object_list [ count = 0 ]]'; l_actual_message := ut3_tester_helper.main_helper.get_failed_expectations(1); --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); @@ -126,7 +123,7 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - l_expected_message := q'[%Actual: ut3_tester_helper.test_dummy_object_list [ count = ] was expected to equal: ut3_tester_helper.test_dummy_object_list [ count = 1 ] + l_expected_message := q'[%Actual: ut3_tester_helper.test_dummy_object_list [ null ] was expected to equal: ut3_tester_helper.test_dummy_object_list [ count = 1 ] %Diff: %Rows: [ 1 differences ] %Row No. 1 - Missing: 1A0]'; @@ -665,7 +662,7 @@ Rows: [ 60 differences, showing first 20 ] g_test_actual := anydata.convertCollection( ut3_tester_helper.t_tab_varchar('A') ); --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); - l_expected_message := q'[%Actual: ut3_tester_helper.t_tab_varchar [ count = 1 ] was expected to equal: ut3_tester_helper.t_tab_varchar [ count = ] + l_expected_message := q'[%Actual: ut3_tester_helper.t_tab_varchar [ count = 1 ] was expected to equal: ut3_tester_helper.t_tab_varchar [ null ] %Diff: %Rows: [ 1 differences ] %Row No. 1 - Extra: A]'; @@ -785,7 +782,7 @@ Rows: [ 60 differences, showing first 20 ] g_test_actual := anydata.convertCollection( ut3_tester_helper.t_varray(1) ); --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); - l_expected_message := q'[%Actual: ut3_tester_helper.t_varray [ count = 1 ] was expected to equal: ut3_tester_helper.t_varray [ count = ] + l_expected_message := q'[%Actual: ut3_tester_helper.t_varray [ count = 1 ] was expected to equal: ut3_tester_helper.t_varray [ null ] %Diff: %Rows: [ 1 differences ] %Row No. 1 - Extra: 1]'; diff --git a/test/ut3_user/expectations/test_expectations_cursor.pkb b/test/ut3_user/expectations/test_expectations_cursor.pkb index 1cb544d44..dc439e7e0 100644 --- a/test/ut3_user/expectations/test_expectations_cursor.pkb +++ b/test/ut3_user/expectations/test_expectations_cursor.pkb @@ -702,7 +702,7 @@ Rows: [ 1 differences ] Diff: Columns: Column data-type is invalid. Expected: NUMBER, actual: VARCHAR2. -Rows: [ all different ] +Rows: [ all different ] All rows are different as the columns position is not matching.]'; l_actual_message := ut3_tester_helper.main_helper.get_failed_expectations(1); --Assert @@ -749,7 +749,7 @@ Columns: Column is misplaced. Expected position: 2, actual position: 4. Column is misplaced. Expected position: 3, actual position: 2. Column is misplaced. Expected position: 4, actual position: 3. -Rows: [ all different ] +Rows: [ all different ] All rows are different as the columns position is not matching.]'; l_actual_message := ut3_tester_helper.main_helper.get_failed_expectations(1); --Assert @@ -1117,7 +1117,7 @@ Rows: [ 4 differences ] %Column [data-type: NUMBER] is missing. Expected column position: 2. %Column <1> [position: 1, data-type: CHAR] is not expected in results. %Column <2> [position: 2, data-type: CHAR] is not expected in results. -%Rows: [ all different ] +%Rows: [ all different ] %All rows are different as the columns position is not matching.]'; l_actual_message := ut3_tester_helper.main_helper.get_failed_expectations(1); --Assert diff --git a/test/ut3_user/expectations/unary/test_expect_to_be_empty.pkb b/test/ut3_user/expectations/unary/test_expect_to_be_empty.pkb index 3694850fe..1447bd1f0 100644 --- a/test/ut3_user/expectations/unary/test_expect_to_be_empty.pkb +++ b/test/ut3_user/expectations/unary/test_expect_to_be_empty.pkb @@ -38,7 +38,7 @@ create or replace package body test_expect_to_be_empty is l_expected_message := q'[Actual: (refcursor [ count = 1 ])% X% -was expected to be empty%%]'; + was expected to be empty%]'; l_actual_message := ut3_tester_helper.main_helper.get_failed_expectations(1); --Assert diff --git a/test/ut3_user/reporters/test_realtime_reporter.pkb b/test/ut3_user/reporters/test_realtime_reporter.pkb index 28724facf..8eb490a11 100644 --- a/test/ut3_user/reporters/test_realtime_reporter.pkb +++ b/test/ut3_user/reporters/test_realtime_reporter.pkb @@ -302,7 +302,7 @@ create or replace package body test_realtime_reporter as procedure single_failed_message is l_actual varchar2(32767); - l_expected varchar2(80) := ''; + l_expected varchar2(80) := ''; begin select t.event_doc.extract( '/event/test/failedExpectations/expectation[1]/message/text()' diff --git a/test/ut3_user/reporters/test_teamcity_reporter.pkb b/test/ut3_user/reporters/test_teamcity_reporter.pkb index fc2ab169e..71d2f857d 100644 --- a/test/ut3_user/reporters/test_teamcity_reporter.pkb +++ b/test/ut3_user/reporters/test_teamcity_reporter.pkb @@ -57,7 +57,7 @@ create or replace package body test_teamcity_reporter as -%##teamcity[testFailed timestamp='%' details='Actual: |'number |[1|] |' (varchar2) was expected to equal: |'number |[2|] |' (varchar2) ' message='Fails as values are different' name='ut3$user#.test_reporters.failing_test'] +%##teamcity[testFailed timestamp='%' details='Actual: |'number |[1|] |' (varchar2) was expected to equal: |'number |[2|] |' (varchar2)' message='Fails as values are different' name='ut3$user#.test_reporters.failing_test'] %##teamcity[testFinished timestamp='%' duration='%' name='ut3$user#.test_reporters.failing_test'] %##teamcity[testStarted timestamp='%' captureStandardOutput='true' name='ut3$user#.test_reporters.erroring_test'] @@ -89,7 +89,7 @@ create or replace package body test_teamcity_reporter as begin l_expected := q'{%##teamcity[testSuiteStarted timestamp='%' name='A suite with |'quote|''] %##teamcity[testStarted timestamp='%' captureStandardOutput='true' name='ut3$user#.check_escape_special_chars.test_do_stuff'] -%##teamcity[testFailed timestamp='%' details='Actual: (varchar2)|n |' |[ |r|n |] |'|nwas expected to be null' name='ut3$user#.check_escape_special_chars.test_do_stuff'] +%##teamcity[testFailed timestamp='%' details='Actual: (varchar2)|n |' |[ |r|n |] |'|n was expected to be null' name='ut3$user#.check_escape_special_chars.test_do_stuff'] %##teamcity[testFinished timestamp='%' duration='%' name='ut3$user#.check_escape_special_chars.test_do_stuff'] %##teamcity[testSuiteFinished timestamp='%' name='A suite with |'quote|'']}'; --act From 2206ff9784ec717899f5f1f01cc184fdd2f037c9 Mon Sep 17 00:00:00 2001 From: Jacek Gebal Date: Mon, 8 Jul 2019 21:39:31 +0100 Subject: [PATCH 4/7] Fixing failing test. --- test/ut3_user/expectations.pkb | 38 +++++++++++++++------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/test/ut3_user/expectations.pkb b/test/ut3_user/expectations.pkb index 942da68f2..3f2ae543d 100644 --- a/test/ut3_user/expectations.pkb +++ b/test/ut3_user/expectations.pkb @@ -1,11 +1,8 @@ create or replace package body expectations as procedure inline_expectation_to_dbms_out is - l_expected sys_refcursor; - l_actual sys_refcursor; - l_output dbmsoutput_linesarray; - l_results ut3.ut_varchar2_list; - l_lines number := 10000; + l_expected clob; + l_actual clob; pragma autonomous_transaction; begin --Arrange @@ -15,24 +12,23 @@ create or replace package body expectations as ut3.ut.expect(0).to_equal(0); --Assert - open l_expected for - select 'FAILURE' as out_row from dual union all - select 'Actual: 1 (number) was expected to equal: 0 (number)' from dual union all - select 'at "UT3$USER#.SOME_PKG.SOME_PROCEDURE", line 4 ut3.ut.expect(1).to_equal(0); + l_actual := ut3_tester_helper.main_helper.get_dbms_output_as_clob(); + + l_expected := q'[FAILURE + Actual: 1 (number) was expected to equal: 0 (number) + at "UT3$USER#.SOME_PKG%", line 4 ut3.ut.expect(1).to_equal(0); at "anonymous block", line 1 - at "UT3$USER#.EXPECTATIONS.INLINE_EXPECTATION_TO_DBMS_OUT", line 13' from dual union all - select 'SUCCESS' from dual union all - select 'Actual: 0 (number) was expected to equal: 0 (number)' from dual union all - select 'FAILURE' as out_row from dual union all - select 'Actual: 1 (number) was expected to equal: 0 (number)' from dual union all - select 'at "UT3$USER#.EXPECTATIONS.INLINE_EXPECTATION_TO_DBMS_OUT", line 14 ut3.ut.expect(1).to_equal(0);' from dual union all - select 'SUCCESS' from dual union all - select 'Actual: 0 (number) was expected to equal: 0 (number)' from dual union all - select '' from dual; - dbms_output.get_lines(lines => l_output, numlines => l_lines); - open l_actual for select trim(column_value) as out_row from table(l_output); + at "UT3$USER#.EXPECTATIONS%", line 10 +SUCCESS + Actual: 0 (number) was expected to equal: 0 (number) +FAILURE + Actual: 1 (number) was expected to equal: 0 (number) + at "UT3$USER#.EXPECTATIONS%", line 11 ut3.ut.expect(1).to_equal(0); +SUCCESS + Actual: 0 (number) was expected to equal: 0 (number) +]'; - ut.expect(l_actual).to_equal(l_expected); + ut.expect(l_actual).to_be_like(l_expected); rollback; end; From 1ea69d8f7e766fb4a681efecc3fb0d4a59b1e323 Mon Sep 17 00:00:00 2001 From: Jacek Gebal Date: Thu, 11 Jul 2019 01:33:53 +0100 Subject: [PATCH 5/7] Updated documentation for expectations. --- docs/userguide/advanced_data_comparison.md | 475 +++++------ docs/userguide/expectations.md | 928 ++++++++++++--------- 2 files changed, 764 insertions(+), 639 deletions(-) diff --git a/docs/userguide/advanced_data_comparison.md b/docs/userguide/advanced_data_comparison.md index 195289392..c45a09643 100644 --- a/docs/userguide/advanced_data_comparison.md +++ b/docs/userguide/advanced_data_comparison.md @@ -49,7 +49,7 @@ When specifying column/attribute names, keep in mind that the names are **case s Consider the following examples ```sql -procedure test_cur_skip_columns_eq is +declare l_expected sys_refcursor; l_actual sys_refcursor; begin @@ -57,8 +57,8 @@ begin open l_actual for select sysdate "ADate", d.* from user_tables d; ut.expect( l_actual ).to_equal( l_expected ).exclude( 'IGNORE_ME,ADate' ); end; - -procedure test_cur_skip_columns_cn is +/ +declare l_expected sys_refcursor; l_actual sys_refcursor; begin @@ -66,42 +66,67 @@ begin open l_actual for select sysdate "ADate", d.* from user_tables d; ut.expect( l_actual ).to_contain( l_expected ).exclude( 'IGNORE_ME,ADate' ); end; +/ ``` +Produces: +``` +SUCCESS + Actual: refcursor [ count = 23 ] was expected to equal: refcursor [ count = 23 ] -Columns 'ignore_me' and "ADate" will get excluded from cursor comparison. -The cursor data is equal or includes expected, when those columns are excluded. +SUCCESS + Actual: refcursor [ count = 23 ] was expected to contain: refcursor [ count = 1 ] +``` -This option is useful in scenarios, when you need to exclude incomparable/unpredictable column data like CREATE_DATE of a record that is maintained by default value on a table column. +Columns 'ignore_me' and "ADate" will get excluded from data comparison. +The actual data is equal/contains expected, when those columns are excluded. + +**Note** +>This option is useful in scenarios, when you need to exclude incomparable/unpredictable column data like CREATE_DATE of a record that is maintained by default value on a table column. ## Selecting columns for data comparison Consider the following example ```sql -procedure include_col_as_csv_eq is - l_actual sys_refcursor; - l_expected sys_refcursor; +declare + l_actual sys_refcursor; + l_expected sys_refcursor; begin - open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; - open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 4; - ut.expect( l_actual ).to_equal( l_expected ).include( 'RN,A_Column,SOME_COL' ); + open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; + open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 4; + ut.expect( l_actual ).to_equal( l_expected ).include( 'RN,A_Column,SOME_COL' ); end; - -procedure include_col_as_csv_cn is - l_actual sys_refcursor; - l_expected sys_refcursor; +/ +declare + l_actual sys_refcursor; + l_expected sys_refcursor; begin - open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; - open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 6; - ut.expect( l_actual ).to_contain( l_expected ).include( 'RN,A_Column,SOME_COL' ); + open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; + open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 6; + ut.expect( l_actual ).to_contain( l_expected ).include( 'RN,A_Column,SOME_COL' ); end; +/ +``` +Produces: ``` +SUCCESS + Actual: refcursor [ count = 3 ] was expected to equal: refcursor [ count = 3 ] + +SUCCESS + Actual: refcursor [ count = 5 ] was expected to contain: refcursor [ count = 3 ] +``` + +Only columns `RN`,`A_Column` and `SOME_COL ` will be included in data comparison. +The actual data is equal/contains expected, when only those columns are included. + +**Note** +>This option can be useful in scenarios where you need to narrow-down the scope of test so that the test is only focused on very specific data. ## Combining include/exclude options 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 -procedure include_col_as_csv_eq is +declare l_actual sys_refcursor; l_expected sys_refcursor; begin @@ -112,8 +137,8 @@ begin .include( ut_varchar2_list( 'A_Column', 'SOME_COL' ) ) .exclude( 'SOME_COL' ); end; - -procedure include_col_as_csv_cn is +/ +declare l_actual sys_refcursor; l_expected sys_refcursor; begin @@ -124,12 +149,21 @@ begin .include( ut_varchar2_list( 'A_Column', 'SOME_COL' ) ) .exclude( 'SOME_COL' ); end; +/ +``` + +Results: +``` +SUCCESS + Actual: refcursor [ count = 3 ] was expected to equal: refcursor [ count = 3 ] +SUCCESS + Actual: refcursor [ count = 5 ] was expected to contain: refcursor [ count = 3 ] ``` Example of `include / exclude` for anydata.convertCollection -```plsql +```sql create or replace type person as object( name varchar2(100), age integer @@ -138,77 +172,46 @@ create or replace type person as object( create or replace type people as table of person / -create or replace package ut_anydata_inc_exc IS - - --%suite(Anydata) - - --%test(Anydata include) - procedure ut_anydata_test_inc; - - --%test(Anydata exclude) - procedure ut_anydata_test_exc; - - --%test(Fail on age) - procedure ut_fail_anydata_test; - -end ut_anydata_inc_exc; -/ - -create or replace package body ut_anydata_inc_exc IS - - procedure ut_anydata_test_inc IS - l_actual people := people(person('Matt',45)); - l_expected people :=people(person('Matt',47)); - begin - ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).include('NAME'); - end; - - procedure ut_anydata_test_exc IS - l_actual people := people(person('Matt',45)); - l_expected people :=people(person('Matt',47)); - begin - --Arrange - ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).exclude('AGE'); - end; +declare + l_actual people := people(person('Matt',45)); + l_expected people :=people(person('Matt',47)); +begin + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).include('NAME'); +end; - procedure ut_fail_anydata_test IS - l_actual people := people(person('Matt',45)); - l_expected people :=people(person('Matt',47)); - begin - --Arrange - ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).include('AGE'); - end; +declare + l_actual people := people(person('Matt',45)); + l_expected people :=people(person('Matt',47)); +begin + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).exclude('AGE'); +end; -end ut_anydata_inc_exc; +declare + l_actual people := people(person('Matt',45)); + l_expected people :=people(person('Matt',47)); +begin + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).include('AGE'); +end; / - ``` -will result in : - -```sql -Anydata - Anydata include [.044 sec] - Anydata exclude [.035 sec] - Fail on age [.058 sec] (FAILED - 1) - -Failures: - - 1) ut_fail_anydata_test - Actual: ut3.people [ count = 1 ] was expected to equal: ut3.people [ count = 1 ] - Diff: - Rows: [ 1 differences ] - Row No. 1 - Actual: 45 - Row No. 1 - Expected: 47 +Results: ``` +SUCCESS + Actual: ut3.people [ count = 1 ] was expected to equal: ut3.people [ count = 1 ] +SUCCESS + Actual: ut3.people [ count = 1 ] was expected to equal: ut3.people [ count = 1 ] +FAILURE + Actual: ut3.people [ count = 1 ] was expected to equal: ut3.people [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Actual: 45 + Row No. 1 - Expected: 47 + at "anonymous block", line 5 -Example of exclude - -Only the columns 'RN', "A_Column" will be compared. Column 'SOME_COL' is excluded. - -This option can be useful in scenarios where you need to narrow-down the scope of test so that the test is only focused on very specific data. +``` ## Unordered @@ -217,35 +220,37 @@ 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 -procedure unordered_tst is - l_actual sys_refcursor; - l_expected sys_refcursor; +declare + l_actual sys_refcursor; + l_expected sys_refcursor; begin - open l_expected for - select username, user_id from all_users - union all - select 'TEST' username, -600 user_id from dual - order by 1 desc; - open l_actual for - select username, user_id from all_users - union all - select 'TEST' username, -610 user_id from dual - order by 1 asc; - ut.expect( l_actual ).to_equal( l_expected ).unordered; + open l_expected for + select username, user_id from all_users + union all + select 'TEST' username, -600 user_id from dual + order by 1 desc; + open l_actual for + select username, user_id from all_users + union all + select 'TEST' username, -610 user_id from dual + order by 1 asc; + ut.expect( l_actual ).to_equal( l_expected ).unordered; end; +/ ``` Above test will result in two differences of one row extra and one row missing. - -```sql - Diff: - Rows: [ 2 differences ] - Missing: TEST-600 - Extra: TEST-610 ``` - +FAILURE + Actual: refcursor [ count = 29 ] was expected to equal: refcursor [ count = 29 ] + Diff: + Rows: [ 2 differences ] + Extra: TEST-610 + Missing: TEST-600 + at "anonymous block", line 15 +``` **Note** - +> `join_by` matcher is much faster on performing data comparison, consider using `join_by` over unordered > `contain` matcher is not considering order of compared data-sets. Using `unordered` makes no difference (it's default) @@ -262,82 +267,99 @@ 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 -procedure join_by_username is - l_actual sys_refcursor; - l_expected sys_refcursor; +declare + l_actual sys_refcursor; + l_expected sys_refcursor; begin - open l_expected for - select username, user_id from all_users - union all - select 'TEST' username, -600 user_id from dual - order by 1 desc; - open l_actual for - select username, user_id from all_users - union all - select 'TEST' username, -610 user_id from dual - order by 1 asc; - ut.expect( l_actual ).to_equal( l_expected ).join_by('USERNAME'); + open l_expected for + select username, user_id from all_users + union all + select 'TEST' username, -600 user_id from dual + order by 1 desc; + open l_actual for + select username, user_id from all_users + union all + select 'TEST' username, -610 user_id from dual + order by 1 asc; + ut.expect( l_actual ).to_equal( l_expected ).join_by('USERNAME'); end; +/ ``` Above test will result in a difference in row 'TEST' regardless of data order. - -```sql - Rows: [ 1 differences ] - PK TEST - Expected: -600 - PK TEST - Actual: -610 +``` +FAILURE + Actual: refcursor [ count = 29 ] was expected to equal: refcursor [ count = 29 ] + Diff: + Rows: [ 1 differences ] + PK TEST - Actual: -610 + PK TEST - Expected: -600 + PK TEST - Extra: TEST-610 + at "anonymous block", line 15 ``` **Note** > When using `join_by`, the join column(s) are displayed first (as PK) to help you identify the mismatched rows/columns. -You can use `join_by` extended syntax in combination with `contain / include ` matcher. +You can use `join_by` syntax in combination with `contain` matcher. ```sql -procedure join_by_username_cn is - l_actual sys_refcursor; - l_expected sys_refcursor; +declare + l_actual sys_refcursor; + l_expected sys_refcursor; begin - open l_actual for select username, user_id from all_users; - open l_expected for - select username, user_id from all_users - union all - select 'TEST' username, -610 user_id from dual; - - ut.expect( l_actual ).to_contain( l_expected ).join_by('USERNAME'); + open l_actual for select username, user_id from all_users; + open l_expected for + select username, user_id from all_users + union all + select 'TEST' username, -610 user_id from dual; + + ut.expect( l_actual ).to_contain( l_expected ).join_by('USERNAME'); end; +/ ``` Above test will indicate that in actual data-set - ```sql - Actual: refcursor [ count = 43 ] was expected to contain: refcursor [ count = 44 ] - Diff: - Rows: [ 1 differences ] - PK TEST - Missing -610 +FAILURE + Actual: refcursor [ count = 28 ] was expected to contain: refcursor [ count = 29 ] + Diff: + Rows: [ 1 differences ] + PK TEST - Missing: TEST-610 + at "anonymous block", line 11 ``` - ### Joining using multiple columns You can specify multiple columns in `join_by` ```sql -procedure test_join_by_many_columns is - l_actual sys_refcursor; - l_expected sys_refcursor; +declare + l_actual sys_refcursor; + l_expected sys_refcursor; begin - open l_expected for - select username, user_id, created from all_users - order by 1 desc; - open l_actual for - select username, user_id, created from all_users - union all - select 'TEST' username, -610 user_id, sysdate from dual - order by 1 asc; - ut.expect( l_actual ).to_equal( l_expected ).join_by('USERNAME, USER_ID'); + open l_expected for + select username, user_id, created from all_users + order by 1 desc; + open l_actual for + select username, user_id, created from all_users + union all + select 'TEST' username, -610 user_id, sysdate from dual + order by 1 asc; + ut.expect( l_actual ).to_equal( l_expected ).join_by('USERNAME, USER_ID'); end; +/ +``` + +Produces: +``` +FAILURE + Actual: refcursor [ count = 29 ] was expected to equal: refcursor [ count = 28 ] + Diff: + Rows: [ 1 differences ] + PK TEST-610 - Extra: TEST-6102019-07-11 + at "anonymous block", line 13 ``` ### Joining using attributes of object in column list @@ -357,33 +379,34 @@ create or replace type person as object( create or replace type people as table of person / -create or replace package test_join_by is ---%suite - ---%test -procedure test_join_by_object_attribute; - -end; -/ - -create or replace package body test_join_by is - procedure test_join_by_object_attribute is - l_actual sys_refcursor; - l_expected sys_refcursor; - begin - open l_expected for - select person('Jack',42) someone from dual union all - select person('Pat', 44) someone from dual union all - select person('Matt',45) someone from dual; - open l_actual for - select person('Matt',55) someone from dual union all - select person('Pat', 44) someone from dual; - ut.expect( l_actual ).to_equal( l_expected ).join_by( 'SOMEONE/NAME' ); - end; - +declare + l_actual sys_refcursor; + l_expected sys_refcursor; +begin + open l_expected for + select person('Jack',42) someone from dual union all + select person('Pat', 44) someone from dual union all + select person('Matt',45) someone from dual; + open l_actual for + select person('Matt',55) someone from dual union all + select person('Pat', 44) someone from dual; + ut.expect( l_actual ).to_equal( l_expected ).join_by( 'SOMEONE/NAME' ); end; / +``` +Produces: +``` +FAILURE + Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 3 ] + Diff: + Rows: [ 2 differences ] + PK Matt - Actual: Matt55 + PK Matt - Actual: 55 + PK Matt - Expected: Matt45 + PK Matt - Expected: 45 + PK Jack - Missing: Jack42 + at "anonymous block", line 12 ``` **Note** @@ -399,15 +422,6 @@ create or replace type person as object( create or replace type people as table of person / -create or replace package test_join_by is ---%suite - ---%test -procedure test_join_by_collection_elem; - -end; -/ - create or replace package body test_join_by is procedure test_join_by_collection_elem is l_actual sys_refcursor; @@ -423,36 +437,51 @@ end; ``` ``` -Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] -Diff: -Unable to join sets: - Join key PERSONS/PERSON/NAME does not exists in expected - Join key PERSONS/PERSON/NAME does not exists in actual - Please make sure that your join clause is not refferring to collection element +FAILURE + Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] + Diff: + Unable to join sets: + Join key PERSONS/PERSON/NAME does not exists in expected + Join key PERSONS/PERSON/NAME does not exists in actual + Please make sure that your join clause is not refferring to collection element + + at "anonymous block", line 7 ``` **Note** ->`join_by` option is slower to process as it needs to perform a cursor join. +>`join_by` option is slower to process as it needs to perform a cursor join. It is still faster than the `unordered`. ## Defining item lists in option -XPath expressions are deprecated. They are currently still supported but in future versions they can be removed completely. Please use a current standard of defining items filter. -When using item list expression, keep in mind the following: +You may provide items for `include`/`exclude`/`join_by` as a single varchar2 value containing comma-separated list of attributes. +You may provide items for `include`/`exclude`/`join_by` as a a ut_varchar2_list of attributes. + +**Note** - object type attributes are nested under `` element - 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 -procedure include_col_list is +declare l_actual sys_refcursor; l_expected sys_refcursor; begin open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 4; ut.expect( l_actual ).to_equal( l_expected ).include( 'RN,A_Column,SOME_COL' ); + open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; + open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 4; ut.expect( l_actual ).to_equal( l_expected ).include( ut_varchar2_list( 'RN', 'A_Column', 'SOME_COL' ) ); end; +/ +``` + +``` +SUCCESS + Actual: refcursor [ count = 3 ] was expected to equal: refcursor [ count = 3 ] +SUCCESS + Actual: refcursor [ count = 3 ] was expected to equal: refcursor [ count = 3 ] ``` ## Unordered columns / uc option @@ -465,44 +494,24 @@ 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 -create or replace package test_unordered_columns as - --%suite - - --%test - procedure cursor_include_unordered_cols; -end; -/ - -create or replace package body test_unordered_columns as - - procedure cursor_include_unordered_cols is - l_actual sys_refcursor; - l_expected sys_refcursor; - begin - --Arrange - open l_actual for select owner, object_name,object_type from all_objects where owner = user - order by 1,2,3 asc; - open l_expected for select object_type, owner, object_name from all_objects where owner = user - and rownum < 20; - - --Assert - ut.expect(l_actual).to_contain(l_expected).unordered_columns(); - end; +declare + l_actual sys_refcursor; + l_expected sys_refcursor; +begin + --Arrange + open l_actual for select owner, object_name, object_type from all_objects where owner = user + order by 1,2,3 asc; + open l_expected for select object_type, owner, object_name from all_objects where owner = user + and rownum < 20; + + --Assert + ut.expect(l_actual).to_contain(l_expected).unordered_columns(); end; / - -exec ut.run('test_unordered_columns'); ``` -The above test is successful despite the fact that column ordering in cursor is different. - +Produces: ``` -test_unordered_columns - cursor_include_unordered_cols [.042 sec] - -Finished in .046193 seconds -1 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) +SUCCESS + Actual: refcursor [ count = 348 ] was expected to contain: refcursor [ count = 19 ] ``` - - - diff --git a/docs/userguide/expectations.md b/docs/userguide/expectations.md index 6f28325a2..391053fd6 100644 --- a/docs/userguide/expectations.md +++ b/docs/userguide/expectations.md @@ -2,6 +2,7 @@ # 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 @@ -25,10 +26,6 @@ Expectation is a combination of: - the matcher used to perform comparison - them matcher parameters (actual value), depending on the matcher type -**Note:** -> The output from expectation is provided directly DBMS_OUTPUT only when the expectation is executed standalone (not as part of unit test). -> 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. Matcher defines the comparison operation to be performed on expected (and actual) value. Pseudo-code: @@ -85,6 +82,90 @@ SUCCESS **Note:** > The examples in the document will be only using shortcut syntax, to keep the document brief. +# 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 a part of 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 +create or replace package test_divide as + --%suite(Divide two numbers) + + --%test(Returns result when divisor is not zero) + procedure divide_6_by_2; + + --%test(Throws exception when divisor is zero) + --%throws(zero_divide) + procedure divide_by_0_throws; +end; +/ + +create or replace package body test_divide as + procedure divide_6_by_2 is + begin + ut.expect(6/2).to_equal(3); + end; + + procedure divide_by_0_throws is + begin + ut.expect(6/0).to_be_not_null(); + end; +end; +/ + +exec ut.run('test_divide'); + +drop package test_divide; +``` + +Produces following outputs: +``` +Package TEST_DIVIDE compiled + + +Package Body TEST_DIVIDE compiled + +Divide two numbers + Returns result when divisor is not zero [.003 sec] + Throws exception when divisor is zero [.003 sec] + +Finished in .009774 seconds +2 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) + + + +PL/SQL procedure successfully completed. + + +Package TEST_DIVIDE dropped. +``` + +Please read about different options for [running test suites](running-unit-tests.md). + +## 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. + +**Important** +> Please do not use expectations as part of your production code. They are not designed to be used as part ot 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 utPLSQL provides the following matchers to perform checks on the expected and actual values. @@ -922,18 +1003,18 @@ end; Returns following output via DBMS_OUTPUT: ``` FAILURE - Actual: ut3_latest_release.ut_varchar2_list [ count = 3 ] was expected to contain: ut3_latest_release.ut_varchar2_list [ count = 3 ] + Actual: ut3.ut_varchar2_list [ count = 3 ] was expected to contain: ut3.ut_varchar2_list [ count = 3 ] Diff: Rows: [ 1 differences ] Missing: E at "anonymous block", line 7 FAILURE - Actual: (ut3_latest_release.ut_varchar2_list [ count = 3 ]) + Actual: (ut3.ut_varchar2_list [ count = 3 ]) Data-types: VARCHAR2 Data: ABC - was expected not to contain:(ut3_latest_release.ut_varchar2_list [ count = 3 ]) + was expected not to contain:(ut3.ut_varchar2_list [ count = 3 ]) Data-types: VARCHAR2 Data: @@ -959,14 +1040,14 @@ end; Returns following output via DBMS_OUTPUT: ``` SUCCESS - Actual: ut3_latest_release.ut_varchar2_list [ count = 4 ] was expected to contain: ut3_latest_release.ut_varchar2_list [ count = 3 ] + Actual: ut3.ut_varchar2_list [ count = 4 ] was expected to contain: ut3.ut_varchar2_list [ count = 3 ] FAILURE - Actual: (ut3_latest_release.ut_varchar2_list [ count = 4 ]) + Actual: (ut3.ut_varchar2_list [ count = 4 ]) Data-types: VARCHAR2 Data: ABCD - was expected not to contain:(ut3_latest_release.ut_varchar2_list [ count = 3 ]) + was expected not to contain:(ut3.ut_varchar2_list [ count = 3 ]) Data-types: VARCHAR2 Data: @@ -992,7 +1073,7 @@ end; Returns following output via DBMS_OUTPUT: ``` FAILURE - Actual: ut3_latest_release.ut_varchar2_list [ count = 3 ] was expected to contain: ut3_latest_release.ut_varchar2_list [ count = 3 ] + Actual: ut3.ut_varchar2_list [ count = 3 ] was expected to contain: ut3.ut_varchar2_list [ count = 3 ] Diff: Rows: [ 3 differences ] Missing: D @@ -1000,12 +1081,12 @@ FAILURE Missing: F at "anonymous block", line 7 SUCCESS - Actual: (ut3_latest_release.ut_varchar2_list [ count = 3 ]) + Actual: (ut3.ut_varchar2_list [ count = 3 ]) Data-types: VARCHAR2 Data: ABC - was expected not to contain:(ut3_latest_release.ut_varchar2_list [ count = 3 ]) + was expected not to contain:(ut3.ut_varchar2_list [ count = 3 ]) Data-types: VARCHAR2 Data: @@ -1013,13 +1094,6 @@ SUCCESS ``` ----------------------------------------------------------------------------------- ----------------------------------------------------------------------------------- - TODO - continue doc rewrite - TODO - fix negated results for contain & equal ----------------------------------------------------------------------------------- ----------------------------------------------------------------------------------- - ## Comparing cursors, object types, nested tables and varrays utPLSQL is capable of comparing compound data-types including: @@ -1089,74 +1163,50 @@ utPLSQL will report all of the above differences in a readable format to help yo Below example illustrates, how utPLSQL will report such differences. ```sql -create or replace package test_cursor_compare as - --%suite - - --%test - procedure do_test; -end; -/ - -create or replace package body test_cursor_compare as - procedure do_test is - l_actual sys_refcursor; - l_expected sys_refcursor; - begin - open l_expected for - select 1 as ID, 'JACK' as FIRST_NAME, 'SPARROW' AS LAST_NAME, 10000 AS SALARY - from dual union all - select 2 as ID, 'LUKE' as FIRST_NAME, 'SKYWALKER' AS LAST_NAME, 1000 AS SALARY - from dual union all - select 3 as ID, 'TONY' as FIRST_NAME, 'STARK' AS LAST_NAME, 100000 AS SALARY - from dual; - open l_actual for - select 'M' AS GENDER, 'JACK' as FIRST_NAME, 'SPARROW' AS LAST_NAME, 1 as ID, '25000' AS SALARY - from dual union all - select 'M' AS GENDER, 'TONY' as FIRST_NAME, 'STARK' AS LAST_NAME, 3 as ID, '100000' AS SALARY - from dual union all - select 'F' AS GENDER, 'JESSICA' as FIRST_NAME, 'JONES' AS LAST_NAME, 4 as ID, '2345' AS SALARY - from dual union all - select 'M' AS GENDER, 'LUKE' as FIRST_NAME, 'SKYWALKER' AS LAST_NAME, 2 as ID, '1000' AS SALARY - from dual; - ut.expect(l_actual).to_equal(l_expected); - end; +declare + l_actual sys_refcursor; + l_expected sys_refcursor; +begin + open l_expected for + select 1 as ID, 'JACK' as FIRST_NAME, 'SPARROW' AS LAST_NAME, 10000 AS SALARY + from dual union all + select 2 as ID, 'LUKE' as FIRST_NAME, 'SKYWALKER' AS LAST_NAME, 1000 AS SALARY + from dual union all + select 3 as ID, 'TONY' as FIRST_NAME, 'STARK' AS LAST_NAME, 100000 AS SALARY + from dual; + open l_actual for + select 'M' AS GENDER, 'JACK' as FIRST_NAME, 'SPARROW' AS LAST_NAME, 1 as ID, '25000' AS SALARY + from dual union all + select 'M' AS GENDER, 'TONY' as FIRST_NAME, 'STARK' AS LAST_NAME, 3 as ID, '100000' AS SALARY + from dual union all + select 'F' AS GENDER, 'JESSICA' as FIRST_NAME, 'JONES' AS LAST_NAME, 4 as ID, '2345' AS SALARY + from dual union all + select 'M' AS GENDER, 'LUKE' as FIRST_NAME, 'SKYWALKER' AS LAST_NAME, 2 as ID, '1000' AS SALARY + from dual; + ut.expect(l_actual).to_equal(l_expected); end; / ``` -When the test package is executed using: - -```sql -set serverout on -exec ut.run('test_cursor_compare'); -``` -We get the following report: +Returns following output via DBMS_OUTPUT: ``` -test_cursor_compare - do_test [.052 sec] (FAILED - 1) - -Failures: - - 1) do_test - Actual: refcursor [ count = 4 ] was expected to equal: refcursor [ count = 3 ] - Diff: - Columns: - Column is misplaced. Expected position: 1, actual position: 4. - Column data-type is invalid. Expected: NUMBER, actual: VARCHAR2. - Column [position: 1, data-type: CHAR] is not expected in results. - Rows: [ 4 differences ] - Row No. 1 - Actual: 25000 - Row No. 1 - Expected: 10000 - Row No. 2 - Actual: TONYSTARK3100000 - Row No. 2 - Expected: 2LUKESKYWALKER1000 - Row No. 3 - Actual: JESSICAJONES42345 - Row No. 3 - Expected: 3TONYSTARK100000 - Row No. 4 - Extra: MLUKESKYWALKER21000 - at "UT3.TEST_CURSOR_COMPARE", line 22 ut.expect(l_actual).to_equal(l_expected); - - -Finished in .053553 seconds -1 tests, 1 failed, 0 errored, 0 disabled, 0 warning(s) +FAILURE + Actual: refcursor [ count = 4 ] was expected to equal: refcursor [ count = 3 ] + Diff: + Columns: + Column is misplaced. Expected position: 1, actual position: 4. + Column data-type is invalid. Expected: NUMBER, actual: VARCHAR2. + Column [position: 1, data-type: CHAR] is not expected in results. + Rows: [ 4 differences ] + Row No. 1 - Actual: 25000 + Row No. 1 - Expected: 10000 + Row No. 2 - Actual: TONYSTARK3100000 + Row No. 2 - Expected: 2LUKESKYWALKER1000 + Row No. 3 - Actual: JESSICAJONES42345 + Row No. 3 - Expected: 3TONYSTARK100000 + Row No. 4 - Extra: MLUKESKYWALKER21000 + Row No. 4 - Extra: MLUKESKYWALKER21000 + at "anonymous block", line 21 ``` utPLSQL identifies and reports on columns: @@ -1186,37 +1236,30 @@ Object type comparison. ```sql create type department as object(name varchar2(30)) / + create or replace function get_dept return department is begin return department('IT'); end; / -create or replace package demo_dept as - --%suite(demo) - --%test(demo of object to object comparison) - procedure test_department; -end; -/ -create or replace package body demo_dept as - procedure test_department is - v_actual department; - begin - --Act/ Assert - ut.expect( anydata.convertObject( get_dept() ) ).to_equal( anydata.convertObject( department('HR') ) ); - end; -end; -/ -begin - ut.run('demo_dept'); -end; -/ +exec ut.expect( anydata.convertObject( get_dept() ) ).to_equal( anydata.convertObject( department('HR') ) ); -drop package demo_dept; drop function get_dept; drop type department; ``` +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: ut3.department was expected to equal: ut3.department + Diff: + Rows: [ 1 differences ] + Row No. 1 - Actual: IT + Row No. 1 - Expected: HR + at "anonymous block", line 1 +``` + Table type comparison. ```sql create type department as object(name varchar2(30)) @@ -1228,87 +1271,215 @@ begin return departments( department('IT'), department('HR') ); end; / -create or replace package demo_depts as - --%suite(demo) - --%test(demo of collection comparison) - procedure test_departments; -end; -/ -create or replace package body demo_depts as - procedure test_departments is - v_expected departments; - v_actual departments; - begin - v_expected := departments(department('HR'), department('IT') ); - ut.expect( anydata.convertCollection( get_depts() ) ).to_equal( anydata.convertCollection( v_expected ) ); - end; -end; -/ +declare + v_expected departments; begin - ut.run('demo_depts'); + v_expected := departments(department('HR'), department('IT') ); + ut.expect( anydata.convertCollection( get_depts() ) ).to_equal( anydata.convertCollection( v_expected ) ); end; / -drop package demo_dept; drop type function get_depts; drop type departments; drop type department; ``` -Some of the possible combinations of the anydata and their results: +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: ut3.departments [ count = 2 ] was expected to equal: ut3.departments [ count = 2 ] + Diff: + Rows: [ 2 differences ] + Row No. 1 - Actual: IT + Row No. 1 - Expected: HR + Row No. 2 - Actual: HR + Row No. 2 - Expected: IT + at "anonymous block", line 5 +``` +Some of the possible combinations of anydata and their results: ```sql +clear screen +set serverout on +set feedback off + create or replace type t_tab_varchar is table of varchar2(1) / - create or replace type dummy_obj as object ( id number, "name" varchar2(30), "Value" varchar2(30) ) / - create or replace type dummy_obj_lst as table of dummy_obj / - create or replace type t_varray is varray(1) of number / +exec ut.expect( anydata.convertObject( dummy_obj( 1, 'A', '0' ) ) ).to_equal( anydata.convertObject( dummy_obj(1, 'A', '0') ) ); +exec ut.expect( anydata.convertCollection( t_tab_varchar('A') ) ).to_equal( anydata.convertCollection( t_tab_varchar('A') ) ); +exec ut.expect( anydata.convertCollection( t_tab_varchar('A') ) ).to_equal( anydata.convertCollection( t_tab_varchar('B') ) ); +exec ut.expect( anydata.convertCollection( t_tab_varchar() ) ).to_be_null(); +exec ut.expect( anydata.convertCollection( t_tab_varchar() ) ).to_equal( anydata.convertCollection( t_tab_varchar() ) ); +exec ut.expect( anydata.convertCollection( t_tab_varchar() ) ).to_equal( anydata.convertCollection( t_tab_varchar('A') ) ); +exec ut.expect( anydata.convertCollection( t_tab_varchar() ) ).to_have_count(0); +exec ut.expect( anydata.convertCollection( t_tab_varchar() ) ).to_equal( anydata.convertCollection( t_tab_varchar() ) ); +exec ut.expect( anydata.convertCollection( t_tab_varchar() ) ).to_equal( anydata.convertCollection( t_tab_varchar('A') ) ); +exec ut.expect( anydata.convertCollection( dummy_obj_lst( dummy_obj( 1, 'A', '0' ) ) ) ).to_equal( anydata.convertCollection( dummy_obj_lst( dummy_obj( 1, 'A', '0' ) ) ) ); +exec ut.expect( anydata.convertCollection( dummy_obj_lst( dummy_obj( 1, 'A', '0' ) ) ) ).to_equal( anydata.convertCollection( dummy_obj_lst( dummy_obj( 2, 'A', '0' ) ) ) ); +exec ut.expect( anydata.convertCollection( dummy_obj_lst() ) ).to_equal( anydata.convertCollection( dummy_obj_lst( dummy_obj( 1, 'A', '0' ) ) ) ); +exec ut.expect( anydata.convertCollection( dummy_obj_lst() ) ).to_be_null(); +exec ut.expect( anydata.convertCollection( dummy_obj_lst() ) ).to_equal( anydata.convertCollection( dummy_obj_lst() ) ); +exec ut.expect( anydata.convertCollection( dummy_obj_lst() ) ).to_have_count(0); +exec ut.expect( anydata.convertCollection( dummy_obj_lst() ) ).to_equal( anydata.convertCollection( dummy_obj_lst(dummy_obj(1, 'A', '0') ) ) ); +exec ut.expect( anydata.convertCollection( dummy_obj_lst() ) ).to_equal( anydata.convertCollection( dummy_obj_lst() ) ); +exec ut.expect( anydata.convertCollection( t_varray() ) ).to_be_null(); +exec ut.expect( anydata.convertCollection( t_varray() ) ).to_equal( anydata.convertCollection( t_varray() ) ); +exec ut.expect( anydata.convertCollection( t_varray() ) ).to_equal( anydata.convertCollection( t_varray(1) ) ); +exec ut.expect( anydata.convertCollection( t_varray() ) ).to_have_count(0); +exec ut.expect( anydata.convertCollection( t_varray() ) ).to_equal( anydata.convertCollection( t_varray() ) ); +exec ut.expect( anydata.convertCollection( t_varray() ) ).to_equal( anydata.convertCollection( t_varray(1) ) ); +exec ut.expect( anydata.convertCollection( t_varray(1) ) ).to_equal( anydata.convertCollection( t_varray(1) ) ); +exec ut.expect( anydata.convertCollection( t_varray(1) ) ).to_equal( anydata.convertCollection( t_varray(2) ) ); + +drop type t_varray; +drop type dummy_obj_lst; +drop type dummy_obj; +drop type t_tab_varchar; ``` +Returns following output via DBMS_OUTPUT: +``` +SUCCESS + Actual: ut3.dummy_obj was expected to equal: ut3.dummy_obj +SUCCESS + Actual: ut3.t_tab_varchar [ count = 1 ] was expected to equal: ut3.t_tab_varchar [ count = 1 ] +FAILURE + Actual: ut3.t_tab_varchar [ count = 1 ] was expected to equal: ut3.t_tab_varchar [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Actual: A + Row No. 1 - Expected: B + at "anonymous block", line 1 + +FAILURE + Actual: (ut3.t_tab_varchar [ count = 0 ]) + Data-types: + VARCHAR2 + Data: + was expected to be null + at "anonymous block", line 1 +SUCCESS + Actual: ut3.t_tab_varchar [ count = 0 ] was expected to equal: ut3.t_tab_varchar [ count = 0 ] + +FAILURE + Actual: ut3.t_tab_varchar [ count = 0 ] was expected to equal: ut3.t_tab_varchar [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Missing: A + at "anonymous block", line 1 + +SUCCESS + Actual: (ut3.t_tab_varchar [ count = 0 ]) was expected to have [ count = 0 ] + +SUCCESS + Actual: ut3.t_tab_varchar [ count = 0 ] was expected to equal: ut3.t_tab_varchar [ count = 0 ] + +FAILURE + Actual: ut3.t_tab_varchar [ count = 0 ] was expected to equal: ut3.t_tab_varchar [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Missing: A + at "anonymous block", line 1 + +SUCCESS + Actual: ut3.dummy_obj_lst [ count = 1 ] was expected to equal: ut3.dummy_obj_lst [ count = 1 ] -| Type A | Comparisoon | Type B | Result | -| :------------------------------------- | :-----------: | :------------------------------------ | -----: | -| t_tab_varchar('A') | equal | t_tab_varchar('A') | Pass | -| t_tab_varchar('A') | equal | t_tab_varchar('B') | Fail | -| t_tab_varchar | is_null | | Pass | -| t_tab_varchar | equal | t_tab_varchar | Pass | -| t_tab_varchar | equal | t_tab_varchar('A') | Fail | -| t_tab_varchar() | have_count(0) | | Pass | -| t_tab_varchar() | equal | t_tab_varchar() | Pass | -| t_tab_varchar() | equal | t_tab_varchar('A') | Fail | -| dummy_obj_lst (dummy_obj(1, 'A', '0')) | equal | dummy_obj_lst(dummy_obj(1, 'A', '0')) | Pass | -| dummy_obj_lst (dummy_obj(1, 'A', '0')) | equal | dummy_obj_lst(dummy_obj(2, 'A', '0')) | Fail | -| dummy_obj_lst | equal | dummy_obj_lst(dummy_obj(1, 'A', '0')) | Fail | -| dummy_obj_lst | is_null | | Pass | -| dummy_obj_lst | equal | dummy_obj_lst | Pass | -| dummy_obj_lst() | have_count(0) | | Pass | -| dummy_obj_lst() | equal | dummy_obj_lst(dummy_obj(1, 'A', '0')) | Fail | -| dummy_obj_lst() | equal | dummy_obj_lst() | Pass | -| t_varray | is null | | Pass | -| t_varray | equal | t_varray | Pass | -| t_varray | equal | t_varray(1) | Fail | -| t_varray() | have_count(0) | | Pass | -| t_varray() | equal | t_varray() | Pass | -| t_varray() | equal | t_varray(1) | Fail | -| t_varray(1) | equal | t_varray(1) | Pass | -| t_varray(1) | equal | t_varray(2) | Fail | +FAILURE + Actual: ut3.dummy_obj_lst [ count = 1 ] was expected to equal: ut3.dummy_obj_lst [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Actual: 1 + Row No. 1 - Expected: 2 + at "anonymous block", line 1 + +FAILURE + Actual: ut3.dummy_obj_lst [ count = 0 ] was expected to equal: ut3.dummy_obj_lst [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Missing: 1A0 + at "anonymous block", line 1 + +FAILURE + Actual: (ut3.dummy_obj_lst [ count = 0 ]) + Data-types: + DUMMY_OBJ + Data: + was expected to be null + at "anonymous block", line 1 + +SUCCESS + Actual: ut3.dummy_obj_lst [ count = 0 ] was expected to equal: ut3.dummy_obj_lst [ count = 0 ] + +SUCCESS + Actual: (ut3.dummy_obj_lst [ count = 0 ]) was expected to have [ count = 0 ] + +FAILURE + Actual: ut3.dummy_obj_lst [ count = 0 ] was expected to equal: ut3.dummy_obj_lst [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Missing: 1A0 + at "anonymous block", line 1 + +SUCCESS + Actual: ut3.dummy_obj_lst [ count = 0 ] was expected to equal: ut3.dummy_obj_lst [ count = 0 ] + +FAILURE + Actual: (ut3.t_varray [ count = 0 ]) + Data-types: + NUMBER + Data: + was expected to be null + at "anonymous block", line 1 + +SUCCESS + Actual: ut3.t_varray [ count = 0 ] was expected to equal: ut3.t_varray [ count = 0 ] + +FAILURE + Actual: ut3.t_varray [ count = 0 ] was expected to equal: ut3.t_varray [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Missing: 1 + at "anonymous block", line 1 + +SUCCESS + Actual: (ut3.t_varray [ count = 0 ]) was expected to have [ count = 0 ] + +SUCCESS + Actual: ut3.t_varray [ count = 0 ] was expected to equal: ut3.t_varray [ count = 0 ] + +FAILURE + Actual: ut3.t_varray [ count = 0 ] was expected to equal: ut3.t_varray [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Missing: 1 + at "anonymous block", line 1 +SUCCESS + Actual: ut3.t_varray [ count = 1 ] was expected to equal: ut3.t_varray [ count = 1 ] +FAILURE + Actual: ut3.t_varray [ count = 1 ] was expected to equal: ut3.t_varray [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Actual: 1 + Row No. 1 - Expected: 2 + at "anonymous block", line 1 +``` ### Comparing cursor data containing DATE fields @@ -1324,74 +1495,79 @@ This way, the DATE data in cursors will be properly formatted for comparison usi 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 +clear screen +alter session set nls_date_format='yyyy-mm-dd'; +set serverout on +set feedback off create table events ( description varchar2(4000), event_date date ) / -create or replace function get_events return sys_refcursor is - l_result sys_refcursor; +declare + c_description constant varchar2(30) := 'Test event'; + c_event_date constant date := to_date('2016-09-08 06:51:22','yyyy-mm-dd hh24:mi:ss'); + c_second constant number := 1/24/60/60; + l_actual sys_refcursor; + l_expected sys_refcursor; begin - open l_result for select description, event_date from events; - return l_result; -end; -/ - -create or replace package test_get_events is - --%suite(get_events) - - --%beforeall - procedure setup_events; - --%test(returns event within date range) - procedure get_events_for_date_range; -end; -/ - -create or replace package body test_get_events is - - gc_description constant varchar2(30) := 'Test event'; - gc_event_date constant date := to_date('2016-09-08 06:51:22','yyyy-mm-dd hh24:mi:ss'); - gc_second constant number := 1/24/60/60; - procedure setup_events is - begin - insert into events (description, event_date) values (gc_description, gc_event_date); - end; + --Arrange + insert into events (description, event_date) values (c_description, c_event_date); - procedure get_events_for_date_range is - l_actual sys_refcursor; - l_expected_bad_date sys_refcursor; begin - --Arrange - ut.set_nls(); -- Change the NLS settings for date to be ISO date-time 'YYYY-MM-DD HH24:MI:SS' - open l_expected_bad_date for select gc_description as description, gc_event_date + gc_second as event_date from dual; + -- Change the NLS settings for date to be ISO date-time 'YYYY-MM-DD HH24:MI:SS' + ut.set_nls(); --Act - l_actual := get_events(); + open l_expected for select c_description as description, c_event_date + c_second as event_date from dual; + open l_actual for select description, event_date from events; --Assert - ut.expect( l_actual ).not_to_equal( l_expected_bad_date ); - ut.reset_nls(); -- Change the NLS settings after cursors were opened + ut.expect( l_actual ).not_to_equal( l_expected ); + -- Reset the NLS settings to their default values after cursor data was processed + ut.reset_nls(); end; - - procedure bad_test is - l_expected_bad_date sys_refcursor; + begin - --Arrange - open l_expected_bad_date for select gc_description as description, gc_event_date + gc_second as event_date from dual; - --Act / Assert - ut.expect( get_events() ).not_to_equal( l_expected_bad_date ); + --Act + open l_expected for select c_description as description, c_event_date + c_second as event_date from dual; + open l_actual for select description, event_date from events; + --Assert + ut.expect( l_actual ).not_to_equal( l_expected ); end; - -end; -/ - -begin - ut.run('test_get_events'); + --Cleanup + rollback; end; / drop table events; -drop function get_events; -drop package test_get_events; ``` + In the above example: -- The test `get_events_for_date_range` will succeed, as the `l_expected_bad_date` cursor contains different date-time then the cursor returned by `get_events` function call. -- The test `bad_test` will fail, as the column `event_date` will get compared as DATE without TIME. +- 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 (suing default current session NLS date format) + +Output via DBMS_OUTPUT from the above example: +``` +SUCCESS + Actual: (refcursor [ count = 1 ]) + Data-types: + VARCHAR2DATE + Data: + Test event2016-09-08T06:51:22 + was expected not to equal: (refcursor [ count = 1 ]) + Data-types: + VARCHAR2DATE + Data: + Test event2016-09-08T06:51:23 +FAILURE + Actual: (refcursor [ count = 1 ]) + Data-types: + VARCHAR2DATE + Data: + Test event2016-09-08 + was expected not to equal: (refcursor [ count = 1 ]) + Data-types: + VARCHAR2DATE + Data: + Test event2016-09-08 + at "anonymous block", line 28 +``` ### Comparing cursor data containing TIMESTAMP bind variables @@ -1400,98 +1576,73 @@ To properly compare `timestamp` column data returned by cursor against bind vari This applies to `timestamp`,`timestamp with timezone`, `timestamp with local timezone` data types. Example below illustrates usage of `cast` operator to assure appropriate precision is applied on timestamp bind-variables in cursor result-set + ```sql -drop table timestamps; +clear screen +set serverout on +set feedback off + create table timestamps ( ts3 timestamp (3), ts6 timestamp (6), ts9 timestamp (9) ); -create or replace package timestamps_api is - procedure load ( - i_timestamp3 timestamps.ts3%type, - i_timestamp6 timestamps.ts6%type, - i_timestamp9 timestamps.ts9%type - ); -end; -/ - -create or replace package body timestamps_api is - procedure load ( - i_timestamp3 timestamps.ts3%type, - i_timestamp6 timestamps.ts6%type, - i_timestamp9 timestamps.ts9%type - ) - is - begin - insert into timestamps (ts3, ts6, ts9) - values (i_timestamp3, i_timestamp6, i_timestamp9); - end; -end; -/ - - -create or replace package test_timestamps_api is - -- %suite +declare + l_time timestamp(9); + l_expected sys_refcursor; + l_actual sys_refcursor; +begin + --Arrange + l_time := systimestamp; - -- %test(Loads data into timestamps table) - procedure test_load; -end; -/ + insert into timestamps (ts3, ts6, ts9) values (l_time, l_time, l_time); -create or replace package body test_timestamps_api is - procedure test_load is - l_time timestamp(9); - l_expected sys_refcursor; - l_actual sys_refcursor; begin - --Arrange - l_time := systimestamp; - + --Act open l_expected for select cast(l_time as timestamp(3)) as ts3, cast(l_time as timestamp(6)) as ts6, cast(l_time as timestamp(9)) as ts9 - from dual; - - --Act - timestamps_api.load ( - l_time, l_time, l_time - ); - + from dual; + + open l_actual for select ts3, ts6, ts9 from timestamps; + --Assert - open l_actual for - select ts3, ts6, ts9 - from timestamps; - - ut.expect (l_actual).to_equal (l_expected); - + ut.expect (l_actual).to_equal (l_expected); + end; + begin + open l_expected for + select l_time as ts3, l_time as ts6, l_time as ts9 from dual; + + open l_actual for select ts3, ts6, ts9 from timestamps; + + --Assert + ut.expect (l_actual).to_equal (l_expected); end; end; / -begin - ut.run ('test_timestamps_api'); -end; -/ +drop table timestamps; ``` -The execution of the above runs successfully +Returns following output via DBMS_OUTPUT: ``` -test_timestamps_api - Loads data into timestamps table [.046 sec] - -Finished in .048181 seconds -1 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) +SUCCESS + Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] +FAILURE + Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] + Diff: + Rows: [ 1 differences ] + Row No. 1 - Actual: 2019-07-08T22:08:41.8992019-07-08T22:08:41.899319 + Row No. 1 - Expected: 2019-07-08T22:08:41.8993190002019-07-08T22:08:41.899319000 + at "anonymous block", line 32 ``` - - # Comparing Json objects -utPLSQL is capable of comparing json data-types on Oracle 12.2 and above. +utPLSQL is capable of comparing json data-types **on Oracle 12.2 and above**. ### Notes on comparison of json data @@ -1502,136 +1653,111 @@ utPLSQL is capable of comparing json data-types on Oracle 12.2 and above. -Some examples of using json data-types in matcher are : - +Compare JSON example: ```sql -create or replace package test_expectations_json is - - --%suite(json expectations) - - --%test(Gives success for identical data) - procedure success_on_same_data; -end; -/ - -create or replace package body test_expectations_json is - - procedure success_on_same_data is - l_expected json_element_t; - l_actual json_element_t; - begin - -- Arrange - l_expected := json_element_t.parse(' -{ - "Actors":[ - { - "name":"Tom Cruise", - "age":56, - "Born At":"Syracuse, NY", - "Birthdate":"July 3, 1962", - "photo":"https://jsonformatter.org/img/tom-cruise.jpg", - "wife":null, - "weight":67.5, - "hasChildren":true, - "hasGreyHair":false, - "children":[ - "Suri", - "Isabella Jane", +declare + l_expected json_element_t; + l_actual json_element_t; +begin + l_expected := json_element_t.parse(' + { + "Actors": [ + { + "name": "Tom Cruise", + "age": 56, + "Birthdate": "July 3, 1962", + "hasChildren": true, + "children": [ "Connor" - ] - }, - { - "name":"Robert Downey Jr.", - "age":53, - "Born At":"New York City, NY", - "Birthdate":"April 4, 1965", - "photo":"https://jsonformatter.org/img/Robert-Downey-Jr.jpg", - "wife":"Susan Downey", - "weight":77.1, - "hasChildren":true, - "hasGreyHair":false, - "children":[ - "Indio Falconer", - "Avri Roel", + ] + }, + { + "name": "Robert Downey Jr.", + "age": 53, + "Birthdate": "April 4, 1965", + "hasChildren": true, + "children": [ "Exton Elias" - ] - } - ] -}'); + ] + } + ] + }' + ); l_actual := json_element_t.parse(' -{ - "Actors":[ - { - "name":"Tom Cruise", - "age":56, - "Born At":"Syracuse, NY", - "Birthdate":"July 3, 1962", - "photo":"https://jsonformatter.org/img/tom-cruise.jpg", - "wife":null, - "weight":67.5, - "hasChildren":true, - "hasGreyHair":false, - "children":[ + { + "Actors": [ + { + "name": "Tom Cruise", + "age": 56, + "Birthdate": "1962.07.03", + "hasChildren": true, + "children": [ "Suri", "Isabella Jane", "Connor" - ] - }, - { - "name":"Robert Downey Jr.", - "age":53, - "Born At":"New York City, NY", - "Birthdate":"April 4, 1965", - "photo":"https://jsonformatter.org/img/Robert-Downey-Jr.jpg", - "wife":"Susan Downey", - "weight":77.1, - "hasChildren":true, - "hasGreyHair":false, - "children":[ + ] + }, + { + "name": "Jr., Robert Downey", + "age": 53, + "Birthdate": "April 4, 1965", + "hasChildren": true, + "children": [ "Indio Falconer", "Avri Roel", "Exton Elias" - ] - } - ] -}'); + ] + } + ] + }' + ); - ut.expect( l_actual ).to_equal( l_actual ); + ut.expect( l_actual ).to_equal( l_expected ); - end; end; -/ ``` -It is possible to use a PL/SQL to extract a piece of JSON and compare it as follow - +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: json was expected to equal: json + Diff: 8 differences found + 4 unequal values, 4 missing properties + Extra property: "Avri Roel" on path: $."Actors"[1]."children"[1] + Extra property: "Isabella Jane" on path: $."Actors"[0]."children"[1] + Extra property: "Connor" on path: $."Actors"[0]."children"[2] + Extra property: "Exton Elias" on path: $."Actors"[1]."children"[2] + Actual value: "Robert Downey Jr." was expected to be: "Jr., Robert Downey" on path: $."Actors"[1]."name" + Actual value: "July 3, 1962" was expected to be: "1962.07.03" on path: $."Actors"[0]."Birthdate" + Actual value: "Connor" was expected to be: "Suri" on path: $."Actors"[0]."children"[0] + Actual value: "Exton Elias" was expected to be: "Indio Falconer" on path: $."Actors"[1]."children"[0] + at "anonymous block", line 59 +``` + +Comparing parts of JSON example: ```sql -create or replace package test_expectations_json is - - --%suite(json expectations) - - --%test(Gives success for identical pieces of two different jsons) - procedure to_diff_json_extract_same; - -end; -/ - -create or replace package body test_expectations_json is - - procedure to_diff_json_extract_same as - l_expected json_object_t; - l_actual json_object_t; - l_array_actual json_array_t; - l_array_expected json_array_t; - begin - -- Arrange - l_expected := json_object_t.parse(' { +declare + l_actual json_object_t; + l_actual_extract json_array_t; + l_expected json_array_t; +begin + -- Arrange + l_expected := json_array_t.parse(' + [ + "Indio Falconer", + "Avri Roel", + "Exton Elias" + ]' + ); + + l_actual := json_object_t.parse(' + { "Actors": [ - { - "name": "Tom Cruise", + { + "name": "Tom Cruise", "age": 56, - "Born At": "Syracuse, NY", + "Born At": "Syracuse, NY", "Birthdate": "July 3, 1962", "photo": "https://jsonformatter.org/img/tom-cruise.jpg", "wife": null, @@ -1645,49 +1771,39 @@ create or replace package body test_expectations_json is ] }, { - "name": "Robert Downey Jr.", + "name": "Robert Downey Jr.", "age": 53, "Born At": "New York City, NY", "Birthdate": "April 4, 1965", "photo": "https://jsonformatter.org/img/Robert-Downey-Jr.jpg", "wife": "Susan Downey", - "weight": 77.1, + "weight": 77.1, "hasChildren": true, "hasGreyHair": false, "children": [ "Indio Falconer", - "Avri Roel", "Exton Elias" ] } ] }' - ); - - l_actual := json_object_t.parse(' { - "Actors": - { - "name": "Krzystof Jarzyna", - "age": 53, - "Born At": "Szczecin", - "Birthdate": "April 4, 1965", - "photo": "niewidzialny", - "wife": "Susan Downey", - "children": [ - "Indio Falconer", - "Avri Roel", - "Exton Elias" - ] - } - }' - ); + ); - l_array_actual := json_array_t(json_query(l_actual.stringify,'$.Actors.children')); - l_array_expected := json_array_t(json_query(l_expected.stringify,'$.Actors[1].children')); - --Act - ut.expect(l_array_actual).to_equal(l_array_expected); + l_actual_extract := json_array_t(json_query(l_actual.stringify,'$.Actors[1].children')); + --Act + ut.expect(l_actual_extract).to_equal(l_expected); - end; end; / ``` + +Returns following output via DBMS_OUTPUT: +``` +FAILURE + Actual: json was expected to equal: json + Diff: 2 differences found + 1 unequal values, 1 missing properties + Missing property: "Exton Elias" on path: $[2] + Actual value: "Avri Roel" was expected to be: "Exton Elias" on path: $[1] + at "anonymous block", line 55 +``` From 773eae25b2ea6982510d0f644609788af6836bf4 Mon Sep 17 00:00:00 2001 From: Jacek Gebal Date: Sat, 13 Jul 2019 03:50:45 +0100 Subject: [PATCH 6/7] Improved functionality of reporters to allow for inline calls. References #955 --- docs/userguide/running-unit-tests.md | 58 ++++++++++++++++++- source/core/types/ut_output_reporter_base.tpb | 9 ++- source/core/types/ut_output_reporter_base.tps | 1 + 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index eb06fd8c3..0afda436c 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -31,6 +31,8 @@ curl -Lk "${DOWNLOAD_URL}" -o utplsql-cli.zip unzip -q utplsql-cli.zip ``` +Keep in mind that you will need to download/provide Oracle JDBC driver separately, as it is not part of utPLSQL-cli due to licensing restrictions. + # ut.run The `ut` package contains overloaded `run` procedures and functions. @@ -167,11 +169,63 @@ The main difference compared to the `ut.run` API is that `ut_runner.run` does no `ut_runner.run` accepts multiple reporters. Each reporter pipes to a separate output (uniquely identified by output_id). Outputs of multiple reporters can be consumed in parallel. This allows for live reporting of test execution progress with threads and several database sessions. -The concept is pretty simple. +`ut_runner.run` API is used by utPLSQL-cli, utPLSQL-SQLDeveloper extension and utPLSQL-maven-plugin and allows for: +- deciding on the scope of test run (by schema names, object names, suite paths or tags ) +- running tests with several concurrent reporters +- real-time reporting of test execution progress +- controlling colored text output to the screen +- controlling scope of code coverage reports +- mapping of database source code to project files +- controlling behavior on test-failures +- controlling client character set for HTML and XML reports +- controlling rollback behavior of test-run +- controlling random order of test execution + +Running with multiple reporters. - in the main thread (session), define the reporters to be used. Each reporter has it's output_id and so you need to extract and store those output_ids. - as a separate thread, start `ut_runner.run` and pass reporters with previously defined output_ids. -- for each reporter start a separate thread and read outputs from the `ut_output_buffer.get_lines` table function by providing the output_id defined in the main thread. +- for each reporter start a separate thread and read outputs from the `reporter.get_lines` table function or from `reporter.get_lines_cursor()` by providing the `reporter_id` defined in the main thread. +- 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 +--main test run ( session 1 ) +declare + l_reporter ut_realtime_reporter := ut_realtime_reporter(); +begin + l_reporter.set_reporter_id( 'd8a79e85915640a6a4e1698fdf90ba74' ); + l_reporter.output_buffer.init(); + ut_runner.run (ut_varchar2_list ('ut3_tester','ut3$user#'), ut_reporters( l_reporter ) ); +end; +/ +``` + +```sql +--report consumer ( session 2 ) +set arraysize 1 +set pagesize 0 + +select * + from table( + ut_realtime_reporter() + .set_reporter_id('d8a79e85915640a6a4e1698fdf90ba74') + .get_lines() + ); +``` + +```sql +--alternative version of report consumer ( session 2 ) +set arraysize 1 +set pagesize 0 + +select + ut_realtime_reporter() + .set_reporter_id('d8a79e85915640a6a4e1698fdf90ba74') + .get_lines_cursor() + from dual; +``` + # Order of test execution diff --git a/source/core/types/ut_output_reporter_base.tpb b/source/core/types/ut_output_reporter_base.tpb index d20f454a8..c4ba73ba5 100644 --- a/source/core/types/ut_output_reporter_base.tpb +++ b/source/core/types/ut_output_reporter_base.tpb @@ -31,7 +31,14 @@ create or replace type body ut_output_reporter_base is overriding member procedure set_reporter_id(self in out nocopy ut_output_reporter_base, a_reporter_id raw) is begin self.id := a_reporter_id; - self.output_buffer.output_id := a_reporter_id; + self.output_buffer.init(a_reporter_id); + end; + + member function set_reporter_id(self in ut_output_reporter_base, a_reporter_id raw) return ut_output_reporter_base is + l_result ut_output_reporter_base := self; + begin + l_result.set_reporter_id(a_reporter_id); + return l_result; end; overriding member procedure before_calling_run(self in out nocopy ut_output_reporter_base, a_run in ut_run) is diff --git a/source/core/types/ut_output_reporter_base.tps b/source/core/types/ut_output_reporter_base.tps index 95ee9c1f8..01350208a 100644 --- a/source/core/types/ut_output_reporter_base.tps +++ b/source/core/types/ut_output_reporter_base.tps @@ -19,6 +19,7 @@ create or replace type ut_output_reporter_base under ut_reporter_base( constructor function ut_output_reporter_base(self in out nocopy ut_output_reporter_base) return self as result, 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), From ac4ca0003cad161836b8ca4ac05625428a6ae5bf Mon Sep 17 00:00:00 2001 From: Jacek Gebal Date: Sat, 13 Jul 2019 04:13:03 +0100 Subject: [PATCH 7/7] Added documentation section on `sys_context( 'UT3_INFO', attribute )`. --- docs/userguide/annotations.md | 107 ++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index 8b8ac890e..8afd6acbd 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1733,6 +1733,113 @@ When processing the test suite `test_employee_pkg` defined in [Example of annota > >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, test and befire/after procedure form within PLSQL procedure using SYS_CONTEXT. + +The information is available by calling `sys_context( 'UT3_INFO', attribute )`. + +Following attributes are populated: +- Always: + - `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) + +Example: +```sql +create or replace procedure which_procecure_called_me is +begin + dbms_output.put_line( + 'Currently running utPLSQL ' ||sys_context( 'ut3_info', 'current_executable_type' ) + ||' ' ||sys_context( 'ut3_info', 'current_executable_name' ) + ); +end; +/ + +create or replace package test_call is + + --%suite + + --%beforeall + procedure beforeall; + + --%beforeeach + procedure beforeeach; + + --%test + procedure test1; + + --%test + procedure test2; + +end; +/ + +create or replace package body test_call is + + procedure beforeall is + begin + which_procecure_called_me(); + dbms_output.put_line('Current test procedure is: '||sys_context('ut3_info','test_name')); + end; + + procedure beforeeach is + begin + which_procecure_called_me(); + dbms_output.put_line('Current test procedure is: '||sys_context('ut3_info','test_name')); + end; + + procedure test1 is + begin + which_procecure_called_me(); + ut.expect(sys_context('ut3_info','suite_package')).to_equal(user||'.test_call'); + end; + + procedure test2 is + begin + which_procecure_called_me(); + ut.expect(sys_context('ut3_info','test_name')).to_equal(user||'.test_call.test2'); + end; + +end; +/ +``` + +```sql +exec ut.run('test_call'); +``` + +``` +test_call + Currently running utPLSQL beforeall UT3.test_call.beforeall + Current test procedure is: + test1 [.008 sec] + Currently running utPLSQL beforeeach UT3.test_call.beforeeach + Current test procedure is: UT3.test_call.test1 + Currently running utPLSQL test UT3.test_call.test1 + test2 [.004 sec] + Currently running utPLSQL beforeeach UT3.test_call.beforeeach + Current test procedure is: UT3.test_call.test2 + Currently running utPLSQL test UT3.test_call.test2 + +Finished in .021295 seconds +2 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) +``` + ## Annotation cache