diff --git a/examples/developer_examples/ut_custom_reporter.tpb b/examples/developer_examples/ut_custom_reporter.tpb index 09991107c..48e8f4a96 100644 --- a/examples/developer_examples/ut_custom_reporter.tpb +++ b/examples/developer_examples/ut_custom_reporter.tpb @@ -16,9 +16,9 @@ create or replace type body ut_custom_reporter is return tab_str; end tab; - overriding member procedure print_text(a_text varchar2) is + overriding member procedure print_text(a_text varchar2, a_item_type varchar2 := null) is begin - (self as ut_documentation_reporter).print_text(tab || a_text); + (self as ut_documentation_reporter).print_text(tab || a_text, a_item_type); end; overriding member procedure before_calling_suite(self in out nocopy ut_custom_reporter, a_suite ut_logical_suite) as diff --git a/examples/developer_examples/ut_custom_reporter.tps b/examples/developer_examples/ut_custom_reporter.tps index aa68c03ad..cc7de666e 100644 --- a/examples/developer_examples/ut_custom_reporter.tps +++ b/examples/developer_examples/ut_custom_reporter.tps @@ -5,7 +5,7 @@ create or replace type ut_custom_reporter under ut_documentation_reporter -- Member functions and procedures constructor function ut_custom_reporter(a_tab_size integer default 4) return self as result, overriding member function tab(self in ut_custom_reporter) return varchar2, - overriding member procedure print_text(a_text varchar2), + overriding member procedure print_text(a_text varchar2, a_item_type varchar2 := null), overriding member procedure before_calling_suite(self in out nocopy ut_custom_reporter, a_suite ut_logical_suite), overriding member procedure before_calling_test(self in out nocopy ut_custom_reporter, a_test ut_test), overriding member procedure after_calling_test(self in out nocopy ut_custom_reporter, a_test ut_test), diff --git a/source/api/ut.pkb b/source/api/ut.pkb index bb599704e..5710f78c4 100644 --- a/source/api/ut.pkb +++ b/source/api/ut.pkb @@ -20,6 +20,9 @@ create or replace package body ut is g_nls_date_format varchar2(4000); gc_fail_on_errors constant boolean := false; + g_result_line_no binary_integer; + g_result_lines ut_varchar2_list := ut_varchar2_list(); + function version return varchar2 is begin return ut_runner.version(); @@ -164,6 +167,31 @@ create or replace package body ut is rollback; end; + function get_report_outputs( a_cursor sys_refcursor ) return varchar2 is + l_clob clob; + l_item_type varchar2(32767); + l_result varchar2(4000); + begin + if g_result_line_no is null then + fetch a_cursor into l_clob, l_item_type; + if a_cursor%notfound then + close a_cursor; + g_result_line_no := null; + g_result_lines := ut_varchar2_list(); + raise_if_packages_invalidated(); + raise no_data_found; + end if; + g_result_lines := ut_utils.clob_to_table(l_clob, ut_utils.gc_max_storage_varchar2_len); + g_result_line_no := g_result_lines.first; + end if; + + if g_result_line_no is not null then + l_result := g_result_lines(g_result_line_no); + g_result_line_no := g_result_lines.next(g_result_line_no); + end if; + return l_result; + end; + function run( a_reporter ut_reporter_base := null, a_color_console integer := 0, @@ -175,8 +203,7 @@ create or replace package body ut is a_client_character_set varchar2 := null ) return ut_varchar2_rows pipelined is l_reporter ut_reporter_base := a_reporter; - l_lines sys_refcursor; - l_line varchar2(4000); + l_results sys_refcursor; begin run_autonomous( ut_varchar2_list(), @@ -190,15 +217,11 @@ create or replace package body ut is a_client_character_set ); if l_reporter is of (ut_output_reporter_base) then - l_lines := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); loop - fetch l_lines into l_line; - exit when l_lines%notfound; - pipe row(l_line); + pipe row( get_report_outputs( l_results ) ); end loop; - close l_lines; end if; - raise_if_packages_invalidated(); return; end; @@ -213,8 +236,7 @@ create or replace package body ut is a_client_character_set varchar2 := null ) return ut_varchar2_rows pipelined is l_reporter ut_reporter_base := a_reporter; - l_lines sys_refcursor; - l_line varchar2(4000); + l_results sys_refcursor; begin run_autonomous( ut_varchar2_list(), @@ -228,15 +250,11 @@ create or replace package body ut is a_client_character_set ); if l_reporter is of (ut_output_reporter_base) then - l_lines := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); loop - fetch l_lines into l_line; - exit when l_lines%notfound; - pipe row(l_line); + pipe row( get_report_outputs( l_results ) ); end loop; - close l_lines; end if; - raise_if_packages_invalidated(); return; end; @@ -252,8 +270,7 @@ create or replace package body ut is a_client_character_set varchar2 := null ) return ut_varchar2_rows pipelined is l_reporter ut_reporter_base := a_reporter; - l_lines sys_refcursor; - l_line varchar2(4000); + l_results sys_refcursor; begin run_autonomous( a_paths, @@ -267,15 +284,11 @@ create or replace package body ut is a_client_character_set ); if l_reporter is of (ut_output_reporter_base) then - l_lines := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); loop - fetch l_lines into l_line; - exit when l_lines%notfound; - pipe row(l_line); + pipe row( get_report_outputs( l_results ) ); end loop; - close l_lines; end if; - raise_if_packages_invalidated(); return; end; @@ -291,8 +304,7 @@ create or replace package body ut is a_client_character_set varchar2 := null ) return ut_varchar2_rows pipelined is l_reporter ut_reporter_base := a_reporter; - l_lines sys_refcursor; - l_line varchar2(4000); + l_results sys_refcursor; begin run_autonomous( a_paths, @@ -306,15 +318,11 @@ create or replace package body ut is a_client_character_set ); if l_reporter is of (ut_output_reporter_base) then - l_lines := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); loop - fetch l_lines into l_line; - exit when l_lines%notfound; - pipe row(l_line); + pipe row( get_report_outputs( l_results ) ); end loop; - close l_lines; end if; - raise_if_packages_invalidated(); return; end; @@ -329,9 +337,8 @@ create or replace package body ut is a_exclude_objects ut_varchar2_list := null, a_client_character_set varchar2 := null ) return ut_varchar2_rows pipelined is - l_reporter ut_reporter_base := a_reporter; - l_lines sys_refcursor; - l_line varchar2(4000); + l_reporter ut_reporter_base := a_reporter; + l_results sys_refcursor; begin run_autonomous( ut_varchar2_list(a_path), @@ -345,15 +352,11 @@ create or replace package body ut is a_client_character_set ); if l_reporter is of (ut_output_reporter_base) then - l_lines := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); loop - fetch l_lines into l_line; - exit when l_lines%notfound; - pipe row(l_line); + pipe row( get_report_outputs( l_results ) ); end loop; - close l_lines; end if; - raise_if_packages_invalidated(); return; end; @@ -369,8 +372,7 @@ create or replace package body ut is a_client_character_set varchar2 := null ) return ut_varchar2_rows pipelined is l_reporter ut_reporter_base := a_reporter; - l_lines sys_refcursor; - l_line varchar2(4000); + l_results sys_refcursor; begin run_autonomous( ut_varchar2_list(a_path), @@ -384,15 +386,11 @@ create or replace package body ut is a_client_character_set ); if l_reporter is of (ut_output_reporter_base) then - l_lines := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); + l_results := treat(l_reporter as ut_output_reporter_base).get_lines_cursor(); loop - fetch l_lines into l_line; - exit when l_lines%notfound; - pipe row(l_line); + pipe row( get_report_outputs( l_results ) ); end loop; - close l_lines; end if; - raise_if_packages_invalidated(); return; end; diff --git a/source/core/events/ut_event_manager.pkb b/source/core/events/ut_event_manager.pkb index 7ae0ad0b3..b48b6d7f4 100644 --- a/source/core/events/ut_event_manager.pkb +++ b/source/core/events/ut_event_manager.pkb @@ -21,15 +21,42 @@ create or replace package body ut_event_manager as type t_listener_numbers is table of boolean index by t_listener_number; type t_events_listeners is table of t_listener_numbers index by t_event_name; - g_event_listeners_index t_events_listeners; - g_listeners t_listeners; + type t_event_manager is record ( + event_listener_index t_events_listeners, + listeners t_listeners + ); + type t_event_managers is table of t_event_manager; + + g_event_listeners_index t_events_listeners; + g_listeners t_listeners; + g_suspended_event_managers t_event_managers; procedure initialize is begin + if g_listeners is not null and g_listeners.count > 0 then + if g_suspended_event_managers is null then + g_suspended_event_managers := t_event_managers(); + end if; + g_suspended_event_managers.extend; + g_suspended_event_managers(g_suspended_event_managers.count).event_listener_index := g_event_listeners_index; + g_suspended_event_managers(g_suspended_event_managers.count).listeners := g_listeners; + end if; g_event_listeners_index.delete; g_listeners := t_listeners(); end; + procedure dispose_listeners is + begin + if g_suspended_event_managers is not null and g_suspended_event_managers.count > 0 then + g_event_listeners_index := g_suspended_event_managers(g_suspended_event_managers.count).event_listener_index; + g_listeners := g_suspended_event_managers(g_suspended_event_managers.count).listeners; + g_suspended_event_managers.trim(1); + else + g_event_listeners_index.delete; + g_listeners := t_listeners(); + end if; + end; + procedure trigger_event( a_event_name t_event_name, a_event_object ut_event_item ) is begin if a_event_name is not null and g_event_listeners_index.exists(a_event_name) @@ -39,6 +66,9 @@ create or replace package body ut_event_manager as for listener_number in 1 .. g_event_listeners_index(a_event_name).count loop g_listeners(listener_number).on_event(a_event_name, a_event_object); end loop; + if a_event_name = ut_utils.gc_finalize then + dispose_listeners; + end if; end if; end; diff --git a/source/core/output_buffers/ut_output_buffer_base.tps b/source/core/output_buffers/ut_output_buffer_base.tps index 6f69047d9..42c4a0d72 100644 --- a/source/core/output_buffers/ut_output_buffer_base.tps +++ b/source/core/output_buffers/ut_output_buffer_base.tps @@ -19,9 +19,10 @@ create or replace type ut_output_buffer_base authid definer as object( output_id raw(32), member procedure init(self in out nocopy ut_output_buffer_base), not instantiable member procedure close(self in ut_output_buffer_base), - not instantiable member procedure send_line(self in ut_output_buffer_base, a_text varchar2), - not instantiable member procedure send_lines(self in ut_output_buffer_base, a_text_list ut_varchar2_rows), - not instantiable member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_varchar2_rows pipelined, + not instantiable member procedure send_line(self in ut_output_buffer_base, a_text varchar2, a_item_type varchar2 := null), + not instantiable member procedure send_lines(self in ut_output_buffer_base, a_text_list ut_varchar2_rows, a_item_type varchar2 := null), + not instantiable member procedure send_clob(self in ut_output_buffer_base, a_text clob, a_item_type varchar2 := null), + not instantiable member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined, not instantiable member function get_lines_cursor(a_initial_timeout natural := null, a_timeout_sec natural := null) return sys_refcursor, not instantiable member procedure lines_to_dbms_output(self in ut_output_buffer_base, a_initial_timeout natural := null, a_timeout_sec natural := null) ) not final not instantiable diff --git a/source/core/output_buffers/ut_output_buffer_tmp.sql b/source/core/output_buffers/ut_output_buffer_tmp.sql index e8127e237..71f8d8178 100644 --- a/source/core/output_buffers/ut_output_buffer_tmp.sql +++ b/source/core/output_buffers/ut_output_buffer_tmp.sql @@ -19,12 +19,14 @@ create table ut_output_buffer_tmp$( */ output_id raw(32) not null, message_id number(38,0) not null, - text varchar2(4000), + text clob, + item_type varchar2(1000), is_finished number(1,0) default 0 not null, constraint ut_output_buffer_tmp_pk primary key(output_id, message_id), constraint ut_output_buffer_tmp_ck check(is_finished = 0 and text is not null or is_finished = 1 and text is null), constraint ut_output_buffer_fk1 foreign key (output_id) references ut_output_buffer_info_tmp$(output_id) ) organization index overflow nologging initrans 100 + lob(text) store as securefile ut_output_text(retention none) ; -- This is needed to be EBR ready as editioning view can only be created by edition enabled user @@ -58,6 +60,7 @@ limitations under the License. select output_id ,message_id ,text + ,item_type ,is_finished from ut_output_buffer_tmp$'; diff --git a/source/core/output_buffers/ut_output_data_row.tps b/source/core/output_buffers/ut_output_data_row.tps new file mode 100644 index 000000000..ddcb65710 --- /dev/null +++ b/source/core/output_buffers/ut_output_data_row.tps @@ -0,0 +1,21 @@ +create or replace type ut_output_data_row as object ( + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 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. + */ + text clob, + item_type varchar2(1000) +) +/ diff --git a/source/core/output_buffers/ut_output_data_rows.tps b/source/core/output_buffers/ut_output_data_rows.tps new file mode 100644 index 000000000..9575231ba --- /dev/null +++ b/source/core/output_buffers/ut_output_data_rows.tps @@ -0,0 +1,19 @@ +create or replace type ut_output_data_rows as + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 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. + */ + table of ut_output_data_row +/ diff --git a/source/core/output_buffers/ut_output_table_buffer.tpb b/source/core/output_buffers/ut_output_table_buffer.tpb index e7045b9df..54adb0f97 100644 --- a/source/core/output_buffers/ut_output_table_buffer.tpb +++ b/source/core/output_buffers/ut_output_table_buffer.tpb @@ -46,38 +46,40 @@ create or replace type body ut_output_table_buffer is commit; end; - overriding member procedure send_line(self in ut_output_table_buffer, a_text varchar2) is + overriding member procedure send_line(self in ut_output_table_buffer, a_text varchar2, a_item_type varchar2 := null) is pragma autonomous_transaction; begin if a_text is not null then - if length(a_text) > ut_utils.gc_max_storage_varchar2_len then - self.send_lines( - ut_utils.convert_collection( - ut_utils.clob_to_table(a_text, ut_utils.gc_max_storage_varchar2_len) - ) - ); - else - insert into ut_output_buffer_tmp(output_id, message_id, text) - values (self.output_id, ut_message_id_seq.nextval, a_text); - end if; - commit; + insert into ut_output_buffer_tmp(output_id, message_id, text, item_type) + values (self.output_id, ut_message_id_seq.nextval, a_text, a_item_type); end if; + commit; end; - overriding member procedure send_lines(self in ut_output_table_buffer, a_text_list ut_varchar2_rows) is + overriding member procedure send_lines(self in ut_output_table_buffer, a_text_list ut_varchar2_rows, a_item_type varchar2 := null) is pragma autonomous_transaction; begin - insert into ut_output_buffer_tmp(output_id, message_id, text) - select self.output_id, ut_message_id_seq.nextval, t.column_value + insert into ut_output_buffer_tmp(output_id, message_id, text, item_type) + select self.output_id, ut_message_id_seq.nextval, t.column_value, a_item_type from table(a_text_list) t where t.column_value is not null; commit; end; + overriding member procedure send_clob(self in ut_output_table_buffer, a_text clob, a_item_type varchar2 := null) is + pragma autonomous_transaction; + begin + if a_text is not null and a_text != empty_clob() then + insert into ut_output_buffer_tmp(output_id, message_id, text, item_type) + values (self.output_id, ut_message_id_seq.nextval, a_text, a_item_type); + end if; + commit; + end; - overriding member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_varchar2_rows pipelined is - l_buffer_data ut_varchar2_rows; + overriding member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined is + l_buffer_data ut_output_data_rows; + l_message_ids ut_integer_list; l_already_waited_for number(10,2) := 0; l_finished boolean := false; lc_init_wait_sec constant naturaln := coalesce(a_initial_timeout, 60 * 60 * 4 ); -- 4 hours @@ -87,22 +89,24 @@ create or replace type body ut_output_table_buffer is lc_long_sleep_time constant number(1) := 1; --sleep for 1 s when waiting long lc_long_wait_time constant number(1) := 1; --waiting more than 1 sec l_sleep_time number(2,1) := lc_short_sleep_time; - function get_data_from_buffer return ut_varchar2_rows is - l_results ut_varchar2_rows; + + procedure remove_read_data(a_message_ids ut_integer_list) is pragma autonomous_transaction; begin - delete from ( - select * - from ut_output_buffer_tmp where output_id = self.output_id order by message_id - ) - returning text bulk collect into l_results; + delete from ut_output_buffer_tmp a + where a.output_id = self.output_id + and a.message_id in (select column_value from table(a_message_ids)); commit; - return l_results; end; begin loop - l_buffer_data := get_data_from_buffer(); + select a.message_id, ut_output_data_row(a.text, a.item_type) + bulk collect into l_message_ids, l_buffer_data + from ut_output_buffer_tmp a + where a.output_id = self.output_id + order by a.message_id; + --nothing fetched from output, wait and try again if l_buffer_data.count = 0 then dbms_lock.sleep(l_sleep_time); @@ -117,7 +121,7 @@ create or replace type body ut_output_table_buffer is l_already_waited_for := 0; l_sleep_time := lc_short_sleep_time; for i in 1 .. l_buffer_data.count loop - if l_buffer_data(i) is not null then + if l_buffer_data(i).text is not null then pipe row(l_buffer_data(i)); else l_finished := true; @@ -125,6 +129,7 @@ create or replace type body ut_output_table_buffer is end if; end loop; end if; + remove_read_data(l_message_ids); exit when l_already_waited_for >= l_wait_for or l_finished; end loop; return; @@ -134,22 +139,27 @@ create or replace type body ut_output_table_buffer is l_lines sys_refcursor; begin open l_lines for - select column_value as text + select text, item_type from table(self.get_lines(a_initial_timeout, a_timeout_sec)); return l_lines; end; overriding member procedure lines_to_dbms_output(self in ut_output_table_buffer, a_initial_timeout natural := null, a_timeout_sec natural := null) is - l_lines sys_refcursor; - l_line varchar2(32767); + l_data sys_refcursor; + l_clob clob; + l_item_type varchar2(32767); + l_lines ut_varchar2_list; begin - l_lines := self.get_lines_cursor(a_initial_timeout, a_timeout_sec); + l_data := self.get_lines_cursor(a_initial_timeout, a_timeout_sec); loop - fetch l_lines into l_line; - exit when l_lines%notfound; - dbms_output.put_line(l_line); + fetch l_data into l_clob, l_item_type; + exit when l_data%notfound; + l_lines := ut_utils.clob_to_table(l_clob); + for i in 1 .. l_lines.count loop + dbms_output.put_line(l_lines(i)); + end loop; end loop; - close l_lines; + close l_data; end; member procedure cleanup_buffer(self in ut_output_table_buffer, a_retention_time_sec natural := null) is diff --git a/source/core/output_buffers/ut_output_table_buffer.tps b/source/core/output_buffers/ut_output_table_buffer.tps index 0e6f83770..1c9acbd1d 100644 --- a/source/core/output_buffers/ut_output_table_buffer.tps +++ b/source/core/output_buffers/ut_output_table_buffer.tps @@ -19,10 +19,11 @@ create or replace type ut_output_table_buffer under ut_output_buffer_base ( start_date date, constructor function ut_output_table_buffer(self in out nocopy ut_output_table_buffer, a_output_id raw := null) return self as result, overriding member procedure init(self in out nocopy ut_output_table_buffer), - overriding member procedure send_line(self in ut_output_table_buffer, a_text varchar2), - overriding member procedure send_lines(self in ut_output_table_buffer, a_text_list ut_varchar2_rows), + overriding member procedure send_line(self in ut_output_table_buffer, a_text varchar2, a_item_type varchar2 := null), + overriding member procedure send_lines(self in ut_output_table_buffer, a_text_list ut_varchar2_rows, a_item_type varchar2 := null), + overriding member procedure send_clob(self in ut_output_table_buffer, a_text clob, a_item_type varchar2 := null), overriding member procedure close(self in ut_output_table_buffer), - overriding member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_varchar2_rows pipelined, + overriding member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined, overriding member function get_lines_cursor(a_initial_timeout natural := null, a_timeout_sec natural := null) return sys_refcursor, overriding member procedure lines_to_dbms_output(self in ut_output_table_buffer, a_initial_timeout natural := null, a_timeout_sec natural := null), member procedure cleanup_buffer(self in ut_output_table_buffer, a_retention_time_sec natural := null) diff --git a/source/core/types/ut_output_reporter_base.tpb b/source/core/types/ut_output_reporter_base.tpb index 3f62d6546..7b875c9e9 100644 --- a/source/core/types/ut_output_reporter_base.tpb +++ b/source/core/types/ut_output_reporter_base.tpb @@ -41,20 +41,25 @@ create or replace type body ut_output_reporter_base is l_output_table_buffer := treat(self.output_buffer as ut_output_table_buffer); end; - member procedure print_text(self in out nocopy ut_output_reporter_base, a_text varchar2) is + member procedure print_text(self in out nocopy ut_output_reporter_base, a_text varchar2, a_item_type varchar2 := null) is begin - self.output_buffer.send_line(a_text); + self.output_buffer.send_line(a_text, a_item_type); end; - member procedure print_text_lines(self in out nocopy ut_output_reporter_base, a_text_lines ut_varchar2_rows) is + member procedure print_text_lines(self in out nocopy ut_output_reporter_base, a_text_lines ut_varchar2_rows, a_item_type varchar2 := null) is begin - self.output_buffer.send_lines(a_text_lines); + self.output_buffer.send_lines(a_text_lines, a_item_type); end; - final member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural) return ut_varchar2_rows pipelined is + member procedure print_clob(self in out nocopy ut_output_reporter_base, a_clob clob, a_item_type varchar2 := null) is begin - for i in (select column_value from table(self.output_buffer.get_lines(a_initial_timeout, a_timeout_sec))) loop - pipe row (i.column_value); + self.output_buffer.send_clob( a_clob, a_item_type ); + end; + + final member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural) return ut_output_data_rows pipelined is + begin + for i in (select value(x) val from table(self.output_buffer.get_lines(a_initial_timeout, a_timeout_sec)) x ) loop + pipe row (i.val); end loop; end; @@ -68,17 +73,6 @@ create or replace type body ut_output_reporter_base is self.output_buffer.lines_to_dbms_output(a_initial_timeout, a_timeout_sec); end; - member procedure print_clob(self in out nocopy ut_output_reporter_base, a_clob clob) is - l_lines ut_varchar2_list; - begin - if a_clob is not null and dbms_lob.getlength(a_clob) > 0 then - l_lines := ut_utils.clob_to_table(a_clob); - for i in 1 .. l_lines.count loop - self.print_text(l_lines(i)); - end loop; - end if; - end; - overriding final member procedure on_finalize(self in out nocopy ut_output_reporter_base, a_run in ut_run) is begin self.output_buffer.close(); diff --git a/source/core/types/ut_output_reporter_base.tps b/source/core/types/ut_output_reporter_base.tps index e23ac9273..cb5c81ea5 100644 --- a/source/core/types/ut_output_reporter_base.tps +++ b/source/core/types/ut_output_reporter_base.tps @@ -21,11 +21,11 @@ create or replace type ut_output_reporter_base under ut_reporter_base( overriding member procedure set_reporter_id(self in out nocopy ut_output_reporter_base, a_reporter_id raw), 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), - member procedure print_text_lines(self in out nocopy ut_output_reporter_base, a_text_lines ut_varchar2_rows), - member procedure print_clob(self in out nocopy ut_output_reporter_base, a_clob clob), + member procedure print_text(self in out nocopy ut_output_reporter_base, a_text varchar2, a_item_type varchar2 := null), + member procedure print_text_lines(self in out nocopy ut_output_reporter_base, a_text_lines ut_varchar2_rows, a_item_type varchar2 := null), + member procedure print_clob(self in out nocopy ut_output_reporter_base, a_clob clob, a_item_type varchar2 := null), - final member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_varchar2_rows pipelined, + final member function get_lines(a_initial_timeout natural := null, a_timeout_sec natural := null) return ut_output_data_rows pipelined, final member function get_lines_cursor(a_initial_timeout natural := null, a_timeout_sec natural := null) return sys_refcursor, final member procedure lines_to_dbms_output(self in ut_output_reporter_base, a_initial_timeout natural := null, a_timeout_sec natural := null), overriding final member procedure on_finalize(self in out nocopy ut_output_reporter_base, a_run in ut_run) diff --git a/source/create_synonyms_and_grants_for_public.sql b/source/create_synonyms_and_grants_for_public.sql index b8daca313..6eaaa669c 100644 --- a/source/create_synonyms_and_grants_for_public.sql +++ b/source/create_synonyms_and_grants_for_public.sql @@ -64,6 +64,8 @@ grant execute on &&ut3_owner..ut_varchar2_rows to public; grant execute on &&ut3_owner..ut_integer_list to public; grant execute on &&ut3_owner..ut_reporter_base to public; grant execute on &&ut3_owner..ut_output_reporter_base to public; +grant execute on &&ut3_owner..ut_output_data_row to public; +grant execute on &&ut3_owner..ut_output_data_rows to public; grant execute on &&ut3_owner..ut_coverage_reporter_base to public; grant execute on &&ut3_owner..ut_console_reporter_base to public; grant execute on &&ut3_owner..ut_coverage to public; @@ -97,6 +99,7 @@ grant execute on &&ut3_owner..ut_annotation_objs_cache_info to public; grant execute on &&ut3_owner..ut_annotation_obj_cache_info to public; grant execute on &&ut3_owner..ut_suite_items_info to public; grant execute on &&ut3_owner..ut_suite_item_info to public; +grant execute on &&ut3_owner..ut_realtime_reporter to public; begin $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then execute immediate 'grant select, insert, delete, update on &&ut3_owner..dbmspcc_blocks to public'; @@ -143,6 +146,8 @@ create public synonym ut_varchar2_rows for &&ut3_owner..ut_varchar2_rows; create public synonym ut_integer_list for &&ut3_owner..ut_integer_list; create public synonym ut_reporter_base for &&ut3_owner..ut_reporter_base; create public synonym ut_output_reporter_base for &&ut3_owner..ut_output_reporter_base; +create public synonym ut_output_data_row for &&ut3_owner..ut_output_data_row; +create public synonym ut_output_data_rows for &&ut3_owner..ut_output_data_rows; create public synonym ut_coverage for &&ut3_owner..ut_coverage; create public synonym ut_coverage_options for &&ut3_owner..ut_coverage_options; create public synonym ut_coverage_helper for &&ut3_owner..ut_coverage_helper; @@ -156,6 +161,7 @@ create public synonym ut_key_value_pair for &&ut3_owner..ut_key_value_pair; create public synonym ut_sonar_test_reporter for &&ut3_owner..ut_sonar_test_reporter; create public synonym ut_suite_items_info for &&ut3_owner..ut_suite_items_info; create public synonym ut_suite_item_info for &&ut3_owner..ut_suite_item_info; +create public synonym ut_realtime_reporter for &&ut3_owner..ut_realtime_reporter; begin $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then execute immediate 'create public synonym dbmspcc_blocks for &&ut3_owner..dbmspcc_blocks'; diff --git a/source/create_user_grants.sql b/source/create_user_grants.sql index 0967a0e8c..bf49e36f1 100644 --- a/source/create_user_grants.sql +++ b/source/create_user_grants.sql @@ -90,6 +90,8 @@ grant execute on &&ut3_owner..ut_coverage to &ut3_user; grant execute on &&ut3_owner..ut_coverage_options to &ut3_user; grant execute on &&ut3_owner..ut_coverage_helper to &ut3_user; grant execute on &&ut3_owner..ut_output_buffer_base to &ut3_user; +grant execute on &&ut3_owner..ut_output_data_row to &ut3_user; +grant execute on &&ut3_owner..ut_output_data_rows to &ut3_user; grant execute on &&ut3_owner..ut_output_table_buffer to &ut3_user; grant execute on &&ut3_owner..ut_file_mappings to &ut3_user; grant execute on &&ut3_owner..ut_file_mapping to &ut3_user; @@ -115,6 +117,7 @@ grant execute on &&ut3_owner..ut_annotation_cache_manager to &ut3_user; grant execute on &&ut3_owner..ut_annotation_parser to &ut3_user; grant execute on &&ut3_owner..ut_annotation_objs_cache_info to &ut3_user; grant execute on &&ut3_owner..ut_annotation_obj_cache_info to &ut3_user; +grant execute on &&ut3_owner..ut_realtime_reporter to &ut3_user; begin $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then execute immediate 'grant select, insert, delete, update on &&ut3_owner..dbmspcc_blocks to &ut3_user'; diff --git a/source/create_user_synonyms.sql b/source/create_user_synonyms.sql index 9a087f61a..97726b1bb 100644 --- a/source/create_user_synonyms.sql +++ b/source/create_user_synonyms.sql @@ -86,6 +86,8 @@ create or replace synonym &ut3_user..ut_varchar2_rows for &&ut3_owner..ut_varcha create or replace synonym &ut3_user..ut_integer_list for &&ut3_owner..ut_integer_list; create or replace synonym &ut3_user..ut_reporter_base for &&ut3_owner..ut_reporter_base; create or replace synonym &ut3_user..ut_output_reporter_base for &&ut3_owner..ut_output_reporter_base; +create or replace synonym &ut3_user..ut_output_data_row for &&ut3_owner..ut_output_data_row; +create or replace synonym &ut3_user..ut_output_data_rows for &&ut3_owner..ut_output_data_rows; create or replace synonym &ut3_user..ut_coverage for &&ut3_owner..ut_coverage; create or replace synonym &ut3_user..ut_coverage_options for &&ut3_owner..ut_coverage_options; create or replace synonym &ut3_user..ut_coverage_helper for &&ut3_owner..ut_coverage_helper; @@ -98,6 +100,7 @@ create or replace synonym &ut3_user..ut_key_value_pairs for &&ut3_owner..ut_key_ create or replace synonym &ut3_user..ut_key_value_pair for &&ut3_owner..ut_key_value_pair; create or replace synonym &ut3_user..ut_compound_data_tmp for &&ut3_owner..ut_cursor_data; create or replace synonym &ut3_user..ut_sonar_test_reporter for &&ut3_owner..ut_sonar_test_reporter; +create or replace synonym &ut3_user..ut_realtime_reporter for &&ut3_owner..ut_realtime_reporter; begin $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then execute immediate 'create or replace synonym &ut3_user..dbmspcc_blocks for &&ut3_owner..dbmspcc_blocks'; diff --git a/source/install.sql b/source/install.sql index 50d70c944..e1478b536 100644 --- a/source/install.sql +++ b/source/install.sql @@ -80,6 +80,8 @@ alter session set current_schema = &&ut3_owner; @@install_component.sql 'core/types/ut_reporters.tps' --output buffer base api +@@install_component.sql 'core/output_buffers/ut_output_data_row.tps' +@@install_component.sql 'core/output_buffers/ut_output_data_rows.tps' @@install_component.sql 'core/output_buffers/ut_output_buffer_base.tps' --output buffer table @@install_component.sql 'core/output_buffers/ut_output_buffer_info_tmp.sql' @@ -292,6 +294,8 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'reporters/ut_coveralls_reporter.tpb' @@install_component.sql 'reporters/ut_coverage_cobertura_reporter.tps' @@install_component.sql 'reporters/ut_coverage_cobertura_reporter.tpb' +@@install_component.sql 'reporters/ut_realtime_reporter.tps' +@@install_component.sql 'reporters/ut_realtime_reporter.tpb' @@install_component.sql 'api/be_between.syn' @@install_component.sql 'api/be_empty.syn' diff --git a/source/reporters/ut_documentation_reporter.tpb b/source/reporters/ut_documentation_reporter.tpb index 73ac3bf1a..df6c25ab0 100644 --- a/source/reporters/ut_documentation_reporter.tpb +++ b/source/reporters/ut_documentation_reporter.tpb @@ -29,13 +29,28 @@ create or replace type body ut_documentation_reporter is return rpad(' ', self.lvl * 2); end tab; - overriding member procedure print_text(self in out nocopy ut_documentation_reporter, a_text varchar2) is + overriding member procedure print_clob(self in out nocopy ut_documentation_reporter, a_clob clob, a_item_type varchar2 := null) is + l_lines ut_varchar2_list; + l_out_lines ut_varchar2_rows := ut_varchar2_rows(); + begin + if a_clob is not null and dbms_lob.getlength(a_clob) > 0 then + l_lines := ut_utils.clob_to_table(a_clob, ut_utils.gc_max_storage_varchar2_len - length(nvl(tab(),0))); + for i in 1 .. l_lines.count loop + if l_lines(i) is not null then + ut_utils.append_to_list(l_out_lines, tab() || l_lines(i) ); + end if; + end loop; + (self as ut_output_reporter_base).print_text_lines(l_out_lines, a_item_type); + end if; + end; + + overriding member procedure print_text(self in out nocopy ut_documentation_reporter, a_text varchar2, a_item_type varchar2 := null) is l_lines ut_varchar2_list; begin if a_text is not null then l_lines := ut_utils.string_to_table(a_text); for i in 1 .. l_lines.count loop - (self as ut_output_reporter_base).print_text(tab || l_lines(i)); + (self as ut_output_reporter_base).print_text(tab || l_lines(i), a_item_type); end loop; end if; end; diff --git a/source/reporters/ut_documentation_reporter.tps b/source/reporters/ut_documentation_reporter.tps index 717ab3cd7..275408a35 100644 --- a/source/reporters/ut_documentation_reporter.tps +++ b/source/reporters/ut_documentation_reporter.tps @@ -20,7 +20,8 @@ create or replace type ut_documentation_reporter under ut_console_reporter_base( constructor function ut_documentation_reporter(self in out nocopy ut_documentation_reporter) return self as result, member function tab(self in ut_documentation_reporter) return varchar2, - overriding member procedure print_text(self in out nocopy ut_documentation_reporter, a_text varchar2), + overriding member procedure print_clob(self in out nocopy ut_documentation_reporter, a_clob clob, a_item_type varchar2 := null), + overriding member procedure print_text(self in out nocopy ut_documentation_reporter, a_text varchar2, a_item_type varchar2 := null), overriding member procedure before_calling_suite(self in out nocopy ut_documentation_reporter, a_suite ut_logical_suite), overriding member procedure after_calling_test(self in out nocopy ut_documentation_reporter, a_test ut_test), overriding member procedure after_calling_after_all (self in out nocopy ut_documentation_reporter, a_executable in ut_executable), diff --git a/source/reporters/ut_realtime_reporter.tpb b/source/reporters/ut_realtime_reporter.tpb new file mode 100644 index 000000000..5d6d3eca5 --- /dev/null +++ b/source/reporters/ut_realtime_reporter.tpb @@ -0,0 +1,269 @@ +create or replace type body ut_realtime_reporter is + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 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_realtime_reporter( + self in out nocopy ut_realtime_reporter + ) return self as result is + begin + self.init($$plsql_unit); + total_number_of_tests := 0; + current_test_number := 0; + current_indent := 0; + print_buffer := ut_varchar2_rows(); + return; + end; + + overriding member procedure before_calling_run( + self in out nocopy ut_realtime_reporter, + a_run in ut_run + ) is + procedure print_test_elements( + a_test in ut_test + ) is + begin + total_number_of_tests := total_number_of_tests + 1; + self.print_start_node('test', 'id', a_test.path); + self.print_node('executableType', a_test.item.executable_type); + self.print_node('ownerName', a_test.item.owner_name); + self.print_node('objectName', a_test.item.object_name); + self.print_node('procedureName', a_test.item.procedure_name); + self.print_node('disabled', case when a_test.get_disabled_flag() then 'true' else 'false' end); + self.print_node('name', a_test.name); + self.print_node('description', a_test.description); + self.print_node('testNumber', to_char(total_number_of_tests)); + self.print_end_node('test'); + end print_test_elements; + + procedure print_suite_elements( + a_suite in ut_logical_suite + ) is + begin + self.print_start_node('suite', 'id', a_suite.path); + self.print_node('name', a_suite.name); + self.print_node('description', a_suite.description); + <> + for i in 1 .. a_suite.items.count loop + if a_suite.items(i) is of(ut_test) then + print_test_elements(treat(a_suite.items(i) as ut_test)); + elsif a_suite.items(i) is of(ut_logical_suite) then + print_suite_elements(treat(a_suite.items(i) as ut_logical_suite)); + end if; + end loop suite_elements; + self.print_end_node('suite'); + end print_suite_elements; + begin + xml_header := ut_utils.get_xml_header(a_run.client_character_set); + self.print_xml_fragment(xml_header); + self.print_start_node('event', 'type', 'pre-run'); + self.print_start_node('suites'); + <> + for i in 1 .. a_run.items.count loop + print_suite_elements(treat(a_run.items(i) as ut_logical_suite)); + end loop items; + self.print_end_node('suites'); + self.print_node('totalNumberOfTests', to_char(total_number_of_tests)); + self.print_end_node('event'); + self.flush_print_buffer('pre-run'); + end before_calling_run; + + overriding member procedure after_calling_run( + self in out nocopy ut_realtime_reporter, + a_run in ut_run + ) is + begin + self.print_xml_fragment(xml_header); + self.print_start_node('event', 'type', 'post-run'); + self.print_end_node('event'); + self.flush_print_buffer('post-run'); + end after_calling_run; + + overriding member procedure before_calling_suite( + self in out nocopy ut_realtime_reporter, + a_suite in ut_logical_suite + ) is + begin + self.print_xml_fragment(xml_header); + self.print_start_node('event', 'type', 'pre-suite'); + self.print_start_node('suite', 'id', a_suite.path); + self.print_end_node('suite'); + self.print_end_node('event'); + self.flush_print_buffer('pre-suite'); + end before_calling_suite; + + overriding member procedure after_calling_suite( + self in out nocopy ut_realtime_reporter, + a_suite in ut_logical_suite + ) is + begin + self.print_xml_fragment(xml_header); + self.print_start_node('event', 'type', 'post-suite'); + self.print_start_node('suite', 'id', a_suite.path); + self.print_node('startTime', to_char(a_suite.start_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF6')); + self.print_node('endTime', to_char(a_suite.end_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF6')); + self.print_node('executionTime', ut_utils.to_xml_number_format(a_suite.execution_time())); + self.print_start_node('counter'); + self.print_node('disabled', to_char(a_suite.results_count.disabled_count)); + self.print_node('success', to_char(a_suite.results_count.success_count)); + self.print_node('failure', to_char(a_suite.results_count.failure_count)); + self.print_node('error', to_char(a_suite.results_count.errored_count)); + self.print_node('warning', to_char(a_suite.results_count.warnings_count)); + self.print_end_node('counter'); + self.print_cdata_node('errorStack', ut_utils.table_to_clob(a_suite.get_error_stack_traces())); + self.print_cdata_node('serverOutput', a_suite.get_serveroutputs()); + self.print_end_node('suite'); + self.print_end_node('event'); + self.flush_print_buffer('post-suite'); + end after_calling_suite; + + overriding member procedure before_calling_test( + self in out nocopy ut_realtime_reporter, + a_test in ut_test + ) is + begin + current_test_number := current_test_number + 1; + self.print_xml_fragment(xml_header); + self.print_start_node('event', 'type', 'pre-test'); + self.print_start_node('test', 'id', a_test.path); + self.print_node('testNumber', to_char(current_test_number)); + self.print_node('totalNumberOfTests', to_char(total_number_of_tests)); + self.print_end_node('test'); + self.print_end_node('event'); + self.flush_print_buffer('pre-test'); + end before_calling_test; + + overriding member procedure after_calling_test( + self in out nocopy ut_realtime_reporter, + a_test in ut_test + ) is + begin + self.print_xml_fragment(xml_header); + self.print_start_node('event', 'type', 'post-test'); + self.print_start_node('test', 'id', a_test.path); + self.print_node('testNumber', to_char(current_test_number)); + self.print_node('totalNumberOfTests', to_char(total_number_of_tests)); + self.print_node('startTime', to_char(a_test.start_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF6')); + self.print_node('endTime', to_char(a_test.end_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF6')); + self.print_node('executionTime', ut_utils.to_xml_number_format(a_test.execution_time())); + self.print_start_node('counter'); + self.print_node('disabled', to_char(a_test.results_count.disabled_count)); + self.print_node('success', to_char(a_test.results_count.success_count)); + self.print_node('failure', to_char(a_test.results_count.failure_count)); + self.print_node('error', to_char(a_test.results_count.errored_count)); + self.print_node('warning', to_char(a_test.results_count.warnings_count)); + self.print_end_node('counter'); + self.print_cdata_node('errorStack', ut_utils.table_to_clob(a_test.get_error_stack_traces())); + self.print_cdata_node('serverOutput', a_test.get_serveroutputs()); + if a_test.failed_expectations.count > 0 then + self.print_start_node('failedExpectations'); + <> + for i in 1 .. a_test.failed_expectations.count loop + self.print_start_node('expectation'); + self.print_node('description', a_test.failed_expectations(i).description); + self.print_cdata_node('message', a_test.failed_expectations(i).message); + self.print_cdata_node('caller', a_test.failed_expectations(i).caller_info); + self.print_end_node('expectation'); + end loop expectations; + self.print_end_node('failedExpectations'); + end if; + self.print_end_node('test'); + self.print_end_node('event'); + self.flush_print_buffer('post-test'); + end after_calling_test; + + overriding member function get_description return varchar2 is + begin + return 'Provides test results in a XML format, for clients such as SQL Developer interested in showing progressing details.'; + end get_description; + + member procedure print_start_node( + self in out nocopy ut_realtime_reporter, + a_node_name in varchar2, + a_attr_name in varchar2 default null, + a_attr_value in varchar2 default null + ) is + begin + self.print_xml_fragment( + '<' || a_node_name + || case + when a_attr_name is not null and a_attr_value is not null then + ' ' || a_attr_name || '="' || dbms_xmlgen.convert(a_attr_value) || '"' + end + || '>', + 0, 1 + ); + end print_start_node; + + member procedure print_end_node( + self in out nocopy ut_realtime_reporter, + a_name in varchar2 + ) is + begin + self.print_xml_fragment('', -1); + end print_end_node; + + member procedure print_node( + self in out nocopy ut_realtime_reporter, + a_name in varchar2, + a_content in clob + ) is + begin + if a_content is not null then + self.print_xml_fragment('<' || a_name || '>' || dbms_xmlgen.convert(a_content) || ''); + end if; + end print_node; + + member procedure print_cdata_node( + self in out nocopy ut_realtime_reporter, + a_name in varchar2, + a_content in clob + ) is + begin + if a_content is not null then + self.print_xml_fragment('<' || a_name || '>'); + end if; + end print_cdata_node; + + member procedure print_xml_fragment( + self in out nocopy ut_realtime_reporter, + a_fragment in clob, + a_indent_summand_before in integer default 0, + a_indent_summand_after in integer default 0 + ) is + begin + current_indent := current_indent + a_indent_summand_before; + ut_utils.append_to_list(print_buffer, lpad(' ', 2 * current_indent) || a_fragment); + current_indent := current_indent + a_indent_summand_after; + end print_xml_fragment; + + member procedure flush_print_buffer( + self in out nocopy ut_realtime_reporter, + a_item_type in varchar2 + ) is + l_doc clob; + l_rows integer := print_buffer.count; + begin + for i in 1 .. l_rows loop + ut_utils.append_to_clob(l_doc, print_buffer(i)); + ut_utils.append_to_clob(l_doc, chr(10)); + end loop; + self.print_clob(l_doc, a_item_type); + print_buffer.delete; + end flush_print_buffer; + +end; +/ diff --git a/source/reporters/ut_realtime_reporter.tps b/source/reporters/ut_realtime_reporter.tps new file mode 100644 index 000000000..49305b247 --- /dev/null +++ b/source/reporters/ut_realtime_reporter.tps @@ -0,0 +1,163 @@ +create or replace type ut_realtime_reporter force under ut_output_reporter_base( + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 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. + */ + + /** + * Cached XML header to be used for every XML document + */ + xml_header varchar2(4000), + + /** + * Total number of all tests in the run (incl. disabled tests). + */ + total_number_of_tests integer, + + /** + * Currently executed test number. + */ + current_test_number integer, + + /** + * Current indentation in logical tabs. + */ + current_indent integer, + + /** + * Buffers lines to be printed. + */ + print_buffer ut_varchar2_rows, + + /** + * The realtime reporter. + * Provides test results in a XML format, for clients such as SQL Developer interested in showing progressing details. + */ + constructor function ut_realtime_reporter( + self in out nocopy ut_realtime_reporter + ) return self as result, + + /** + * Provides meta data of complete run in advance. + */ + overriding member procedure before_calling_run( + self in out nocopy ut_realtime_reporter, + a_run in ut_run + ), + + /** + * Indicates the end of the test run. + */ + overriding member procedure after_calling_run( + self in out nocopy ut_realtime_reporter, + a_run in ut_run + ), + + /** + * Indicates the start of a test suite execution. + */ + overriding member procedure before_calling_suite( + self in out nocopy ut_realtime_reporter, + a_suite in ut_logical_suite + ), + + /** + * Provides meta data of completed test suite. + */ + overriding member procedure after_calling_suite( + self in out nocopy ut_realtime_reporter, + a_suite in ut_logical_suite + ), + + /** + * Indicates the start of a test. + */ + overriding member procedure before_calling_test( + self in out nocopy ut_realtime_reporter, + a_test in ut_test + ), + + /** + * Provides meta data of a completed test. + */ + overriding member procedure after_calling_test( + self in out nocopy ut_realtime_reporter, + a_test in ut_test + ), + + /** + * Provides the description of this reporter. + */ + overriding member function get_description return varchar2, + + /** + * Prints the start tag of a XML node with an optional attribute. + */ + member procedure print_start_node( + self in out nocopy ut_realtime_reporter, + a_node_name in varchar2, + a_attr_name in varchar2 default null, + a_attr_value in varchar2 default null + ), + + /** + * Prints the end tag of a XML node. + */ + member procedure print_end_node( + self in out nocopy ut_realtime_reporter, + a_name in varchar2 + ), + + /** + * Prints a child node with content. Special characters are encoded. + */ + member procedure print_node( + self in out nocopy ut_realtime_reporter, + a_name in varchar2, + a_content in clob + ), + + /** + * Prints a child node with content. Content is passed 1:1 using CDATA. + */ + member procedure print_cdata_node( + self in out nocopy ut_realtime_reporter, + a_name in varchar2, + a_content in clob + ), + + /** + * Prints a line of the resulting XML document using the current indentation. + * a_indent_summand_before is added before printing a line. + * a_indent_summand_after is added after printing a line. + * All output is produced through this function. + */ + member procedure print_xml_fragment( + self in out nocopy ut_realtime_reporter, + a_fragment in clob, + a_indent_summand_before in integer default 0, + a_indent_summand_after in integer default 0 + ), + + /** + * Flushes the local print buffer to the output buffer. + */ + member procedure flush_print_buffer( + self in out nocopy ut_realtime_reporter, + a_item_type in varchar2 + ) +) +not final +/ diff --git a/source/uninstall_objects.sql b/source/uninstall_objects.sql index 97f2c5a5d..f510acc2a 100644 --- a/source/uninstall_objects.sql +++ b/source/uninstall_objects.sql @@ -39,6 +39,8 @@ drop type ut_coverage_html_reporter force; drop type ut_sonar_test_reporter force; +drop type ut_realtime_reporter force; + drop package ut_coverage; drop package ut_coverage_helper; @@ -239,6 +241,10 @@ drop table ut_output_buffer_info_tmp$; drop sequence ut_message_id_seq; +drop type ut_output_data_rows force; + +drop type ut_output_data_row force; + drop type ut_results_counter force; drop type ut_expectation_results force; diff --git a/test/api/test_ut_runner.pkb b/test/api/test_ut_runner.pkb index 96ae08a94..1cbf5d188 100644 --- a/test/api/test_ut_runner.pkb +++ b/test/api/test_ut_runner.pkb @@ -301,6 +301,7 @@ end;'; select 'UT3.UT_COVERALLS_REPORTER', 'Y' from dual union all select 'UT3.UT_DOCUMENTATION_REPORTER', 'Y' from dual union all select 'UT3.UT_JUNIT_REPORTER', 'Y' from dual union all + select 'UT3.UT_REALTIME_REPORTER', 'Y' from dual union all select 'UT3.UT_SONAR_TEST_REPORTER', 'Y' from dual union all select 'UT3.UT_TEAMCITY_REPORTER', 'Y' from dual union all select 'UT3.UT_TFS_JUNIT_REPORTER', 'Y' from dual union all diff --git a/test/core/reporters/test_realtime_reporter.pkb b/test/core/reporters/test_realtime_reporter.pkb new file mode 100644 index 000000000..aec0faee2 --- /dev/null +++ b/test/core/reporters/test_realtime_reporter.pkb @@ -0,0 +1,350 @@ +create or replace package body test_realtime_reporter as + + g_events test_event_list := test_event_list(); + + procedure create_test_suites_and_run is + pragma autonomous_transaction; + begin + execute immediate q'[create or replace package check_realtime_reporting1 is + --%suite(suite ) + --%suitepath(realtime_reporting) + + --%context(test context) + + --%test(test 1 - OK) + procedure test_1_ok; + + --%test(test 2 - NOK) + procedure test_2_nok; + + --%endcontext + end;]'; + execute immediate q'[create or replace package body check_realtime_reporting1 is + procedure test_1_ok is + begin + ut3.ut.expect(1).to_equal(1); + end; + + procedure test_2_nok is + begin + ut3.ut.expect(1).to_equal(2); + end; + end;]'; + + execute immediate q'[create or replace package check_realtime_reporting2 is + --%suite + --%suitepath(realtime_reporting) + + --%test + procedure test_3_ok; + + --%test + procedure test_4_nok; + + --%test + --%disabled + procedure test_5; + end;]'; + execute immediate q'[create or replace package body check_realtime_reporting2 is + procedure test_3_ok is + begin + ut3.ut.expect(2).to_equal(2); + end; + + procedure test_4_nok is + begin + ut3.ut.expect(2).to_equal(3); + ut3.ut.expect(2).to_equal(4); + end; + + procedure test_5 is + begin + null; + end; + end;]'; + + execute immediate q'[create or replace package check_realtime_reporting3 is + --%suite + --%suitepath(realtime_reporting) + + --%test + procedure test_6_with_runtime_error; + + --%test + procedure test_7_with_serveroutput; + + --%afterall + procedure print_and_raise; + end;]'; + execute immediate q'[create or replace package body check_realtime_reporting3 is + procedure test_6_with_runtime_error is + l_actual integer; + begin + execute immediate 'select 6 from non_existing_table' into l_actual; + ut3.ut.expect(6).to_equal(l_actual); + end; + + procedure test_7_with_serveroutput is + begin + dbms_output.put_line('before test 7'); + ut3.ut.expect(7).to_equal(7); + dbms_output.put_line('after test 7'); + end; + + procedure print_and_raise is + begin + dbms_output.put_line('Now, a no_data_found exception is raised'); + dbms_output.put_line('dbms_output and error stack is reported for this suite.'); + dbms_output.put_line('A runtime error in afterall is counted as a warning.'); + raise no_data_found; + end; + end;]'; + + <> + declare + l_reporter ut3.ut_realtime_reporter := ut3.ut_realtime_reporter(); + begin + -- produce + ut3.ut_runner.run( + a_paths => ut3.ut_varchar2_list(':realtime_reporting'), + a_reporters => ut3.ut_reporters(l_reporter) + ); + -- consume + select test_event_object(item_type, xmltype(text)) + bulk collect into g_events + from table(ut3.ut_output_table_buffer(l_reporter.output_buffer.output_id).get_lines()); + end run_report_and_cache_result; + end create_test_suites_and_run; + + procedure xml_report_structure is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + open l_actual for + select t.event_doc.extract('/event/@type').getstringval() as event_type, + t.event_doc.extract('/event/suite/@id|/event/test/@id').getstringval() as item_id + from table(g_events) t; + open l_expected for + select 'pre-run' as event_type, null as item_id from dual union all + select 'pre-suite' as event_type, 'realtime_reporting' as item_id from dual union all + select 'pre-suite' as event_type, 'realtime_reporting.check_realtime_reporting3' as item_id from dual union all + select 'pre-test' as event_type, 'realtime_reporting.check_realtime_reporting3.test_6_with_runtime_error' as item_id from dual union all + select 'post-test' as event_type, 'realtime_reporting.check_realtime_reporting3.test_6_with_runtime_error' as item_id from dual union all + select 'pre-test' as event_type, 'realtime_reporting.check_realtime_reporting3.test_7_with_serveroutput' as item_id from dual union all + select 'post-test' as event_type, 'realtime_reporting.check_realtime_reporting3.test_7_with_serveroutput' as item_id from dual union all + select 'post-suite' as event_type, 'realtime_reporting.check_realtime_reporting3' as item_id from dual union all + select 'pre-suite' as event_type, 'realtime_reporting.check_realtime_reporting2' as item_id from dual union all + select 'pre-test' as event_type, 'realtime_reporting.check_realtime_reporting2.test_3_ok' as item_id from dual union all + select 'post-test' as event_type, 'realtime_reporting.check_realtime_reporting2.test_3_ok' as item_id from dual union all + select 'pre-test' as event_type, 'realtime_reporting.check_realtime_reporting2.test_4_nok' as item_id from dual union all + select 'post-test' as event_type, 'realtime_reporting.check_realtime_reporting2.test_4_nok' as item_id from dual union all + select 'pre-test' as event_type, 'realtime_reporting.check_realtime_reporting2.test_5' as item_id from dual union all + select 'post-test' as event_type, 'realtime_reporting.check_realtime_reporting2.test_5' as item_id from dual union all + select 'post-suite' as event_type, 'realtime_reporting.check_realtime_reporting2' as item_id from dual union all + select 'pre-suite' as event_type, 'realtime_reporting.check_realtime_reporting1' as item_id from dual union all + select 'pre-suite' as event_type, 'realtime_reporting.check_realtime_reporting1.test context' as item_id from dual union all + select 'pre-test' as event_type, 'realtime_reporting.check_realtime_reporting1.test context.test_1_ok' as item_id from dual union all + select 'post-test' as event_type, 'realtime_reporting.check_realtime_reporting1.test context.test_1_ok' as item_id from dual union all + select 'pre-test' as event_type, 'realtime_reporting.check_realtime_reporting1.test context.test_2_nok' as item_id from dual union all + select 'post-test' as event_type, 'realtime_reporting.check_realtime_reporting1.test context.test_2_nok' as item_id from dual union all + select 'post-suite' as event_type, 'realtime_reporting.check_realtime_reporting1.test context' as item_id from dual union all + select 'post-suite' as event_type, 'realtime_reporting.check_realtime_reporting1' as item_id from dual union all + select 'post-suite' as event_type, 'realtime_reporting' as item_id from dual union all + select 'post-run' as event_type, null as item_id from dual; + ut.expect(l_actual).to_equal(l_expected); + end xml_report_structure; + + procedure total_number_of_tests is + l_actual integer; + l_expected integer := 7; + begin + select t.event_doc.extract('/event/totalNumberOfTests/text()').getnumberval() + into l_actual + from table(g_events) t + where t.event_type = 'pre-run'; + end total_number_of_tests; + + procedure escaped_characters is + l_actual varchar2(32767); + l_expected varchar2(20) := 'suite <A>'; + begin + select t.event_doc.extract( + '//suite[@id="realtime_reporting.check_realtime_reporting1"]/description/text()' + ).getstringval() + into l_actual + from table(g_events) t + where t.event_type = 'pre-run'; + ut.expect(l_actual).to_equal(l_expected); + end escaped_characters; + + procedure pre_test_nodes is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + open l_actual for + select t.event_doc.extract('//test/testNumber/text()') + .getnumberval() as test_number, + t.event_doc.extract('//test/totalNumberOfTests/text()') + .getnumberval() as total_number_of_tests + from table(g_events) t + where t.event_type = 'pre-test' + and t.event_doc.extract('//test/@id').getstringval() is not null; + open l_expected for + select level as test_number, + 7 as total_number_of_tests + from dual + connect by level <= 7; + ut.expect(l_actual).to_equal(l_expected).unordered; + end pre_test_nodes; + + procedure post_test_nodes is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + open l_actual for + select t.event_doc.extract('//test/testNumber/text()') + .getnumberval() as test_number, + t.event_doc.extract('//test/totalNumberOfTests/text()') + .getnumberval() as total_number_of_tests + from table(g_events) t + where t.event_type = 'post-test' + and t.event_doc.extract('//test/@id').getstringval() is not null + and t.event_doc.extract('//test/startTime/text()').getstringval() is not null + and t.event_doc.extract('//test/endTime/text()').getstringval() is not null + and t.event_doc.extract('//test/executionTime/text()').getnumberval() is not null + and t.event_doc.extract('//test/counter/disabled/text()').getnumberval() is not null + and t.event_doc.extract('//test/counter/success/text()').getnumberval() is not null + and t.event_doc.extract('//test/counter/failure/text()').getnumberval() is not null + and t.event_doc.extract('//test/counter/error/text()').getnumberval() is not null + and t.event_doc.extract('//test/counter/warning/text()').getnumberval() is not null; + open l_expected for + select level as test_number, + 7 as total_number_of_tests + from dual + connect by level <= 7; + ut.expect(l_actual).to_equal(l_expected).unordered; + end post_test_nodes; + + procedure single_failed_message is + l_actual varchar2(32767); + l_expected varchar2(80) := ''; + begin + select t.event_doc.extract( + '/event/test/failedExpectations/expectation[1]/message/text()' + ).getstringval() + into l_actual + from table(g_events) t + where t.event_doc.extract('/event[@type="post-test"]/test/@id').getstringval() + = 'realtime_reporting.check_realtime_reporting1.test context.test_2_nok'; + ut.expect(l_actual).to_equal(l_expected); + end single_failed_message; + + procedure multiple_failed_messages is + l_actual integer; + l_expected integer := 2; + begin + select count(*) + into l_actual + from table(g_events) t, + xmltable( + '/event/test/failedExpectations/expectation' + passing t.event_doc + columns message clob path 'message', + caller clob path 'caller' + ) x + where t.event_doc.extract('/event[@type="post-test"]/test/@id').getstringval() + = 'realtime_reporting.check_realtime_reporting2.test_4_nok' + and x.message is not null + and x.caller is not null; + ut.expect(l_actual).to_equal(l_expected); + end multiple_failed_messages; + + procedure serveroutput_of_test is + l_actual clob; + l_expected_list ut3.ut_varchar2_list; + l_expected clob; + begin + select t.event_doc.extract('//event/test/serverOutput/text()').getstringval() + into l_actual + from table(g_events) t + where t.event_doc.extract('/event[@type="post-test"]/test/@id').getstringval() + = 'realtime_reporting.check_realtime_reporting3.test_7_with_serveroutput'; + ut3.ut_utils.append_to_list(l_expected_list, ''); + l_expected := ut3.ut_utils.table_to_clob(l_expected_list); + ut.expect(l_actual).to_equal(l_expected); + end serveroutput_of_test; + + procedure serveroutput_of_testsuite is + l_actual clob; + l_expected_list ut3.ut_varchar2_list; + l_expected clob; + begin + select t.event_doc.extract('//event/suite/serverOutput/text()').getstringval() + into l_actual + from table(g_events) t + where t.event_doc.extract('/event[@type="post-suite"]/suite/@id').getstringval() + = 'realtime_reporting.check_realtime_reporting3'; + ut3.ut_utils.append_to_list(l_expected_list, ''); + l_expected := ut3.ut_utils.table_to_clob(l_expected_list); + ut.expect(l_actual).to_equal(l_expected); + end serveroutput_of_testsuite; + + procedure error_stack_of_test is + l_actual clob; + l_expected_list ut3.ut_varchar2_list; + l_expected clob; + begin + select t.event_doc.extract('//event/test/errorStack/text()').getstringval() + into l_actual + from table(g_events) t + where t.event_doc.extract('/event[@type="post-test"]/test/@id').getstringval() + = 'realtime_reporting.check_realtime_reporting3.test_6_with_runtime_error'; + ut3.ut_utils.append_to_list(l_expected_list, ''); + l_expected := ut3.ut_utils.table_to_clob(l_expected_list); + ut.expect(l_actual).to_be_like(l_expected); + end error_stack_of_test; + + procedure error_stack_of_testsuite is + l_actual clob; + l_expected_list ut3.ut_varchar2_list; + l_expected clob; + begin + select t.event_doc.extract('//event/suite/errorStack/text()').getstringval() + into l_actual + from table(g_events) t + where t.event_doc.extract('/event[@type="post-suite"]/suite/@id').getstringval() + = 'realtime_reporting.check_realtime_reporting3'; + ut3.ut_utils.append_to_list(l_expected_list, ''); + l_expected := ut3.ut_utils.table_to_clob(l_expected_list); + ut.expect(l_actual).to_be_like(l_expected); + end error_stack_of_testsuite; + + procedure get_description is + l_reporter ut3.ut_realtime_reporter; + l_actual varchar2(4000); + l_expected varchar2(80) := '%SQL Developer%'; + begin + l_reporter := ut3.ut_realtime_reporter(); + l_actual := l_reporter.get_description(); + ut.expect(l_actual).to_be_like(l_expected); + end get_description; + + procedure remove_test_suites is + pragma autonomous_transaction; + begin + execute immediate 'drop package check_realtime_reporting1'; + execute immediate 'drop package check_realtime_reporting2'; + execute immediate 'drop package check_realtime_reporting3'; + end remove_test_suites; + +end test_realtime_reporter; +/ diff --git a/test/core/reporters/test_realtime_reporter.pks b/test/core/reporters/test_realtime_reporter.pks new file mode 100644 index 000000000..7b5862b15 --- /dev/null +++ b/test/core/reporters/test_realtime_reporter.pks @@ -0,0 +1,49 @@ +create or replace package test_realtime_reporter as + + --%suite(ut_realtime_reporter) + --%suitepath(utplsql.core.reporters) + + --%beforeall + procedure create_test_suites_and_run; + + --%test(Provide a report structure with pre-run information and event based messages per suite and per test) + procedure xml_report_structure; + + --%test(Provide the total number of tests as part of the pre-run information structure) + procedure total_number_of_tests; + + --%test(Escape special characters in data such as the test suite description) + procedure escaped_characters; + + --%test(Provide a node before starting a test with testNumber and totalNumberOfTests) + procedure pre_test_nodes; + + --%test(Provide a node after completion of a test with test results) + procedure post_test_nodes; + + --%test(Provide expectation message for a failed test) + procedure single_failed_message; + + --%test(Provide expectation messages for each failed assertion of a failed test) + procedure multiple_failed_messages; + + --%test(Provide dbms_output produced in a test) + procedure serveroutput_of_test; + + --%test(Provide dbms_output produced in a testsuite) + procedure serveroutput_of_testsuite; + + --%test(Provide the error stack of a test) + procedure error_stack_of_test; + + --%test(Provide the error stack of a testsuite) + procedure error_stack_of_testsuite; + + --%test(Provide a description of the reporter explaining the use for SQL Developer) + procedure get_description; + + --%afterall + procedure remove_test_suites; + +end test_realtime_reporter; +/ diff --git a/test/core/test_output_buffer.pkb b/test/core/test_output_buffer.pkb index a22f3772d..f7d3cbb8b 100644 --- a/test/core/test_output_buffer.pkb +++ b/test/core/test_output_buffer.pkb @@ -1,18 +1,30 @@ create or replace package body test_output_buffer is - procedure test_recieve is - l_result varchar2(4000); - l_remaining integer; - l_expected varchar2(4000); - l_buffer ut3.ut_output_buffer_base := ut3.ut_output_table_buffer(); + procedure test_receive is + l_actual_text clob; + l_actual_item_type varchar2(1000); + l_remaining integer; + l_expected_text clob; + l_expected_item_type varchar2(1000); + l_buffer ut3.ut_output_buffer_base; begin - --Act - l_expected := lpad('a text',4000,',a text'); - l_buffer.send_line(l_expected); + --Arrange + l_buffer := ut3.ut_output_table_buffer(); + l_expected_text := to_clob(lpad('a text', 31000, ',a text')) + || chr(10) || to_clob(lpad('a text', 31000, ',a text')) + || chr(13) || to_clob(lpad('a text', 31000, ',a text')) + || chr(13) || chr(10) || to_clob(lpad('a text', 31000, ',a text')) || to_clob(lpad('a text', 31000, ',a text')); + l_expected_item_type := lpad('some item type',1000,'-'); + --Act + l_buffer.send_clob(l_expected_text, l_expected_item_type); - select * into l_result from table(l_buffer.get_lines(0,0)); + select text, item_type + into l_actual_text, l_actual_item_type + from table(l_buffer.get_lines(0,0)); - ut.expect(l_result).to_equal(l_expected); + --Assert + ut.expect(l_actual_text).to_equal(l_expected_text); + ut.expect(l_actual_item_type).to_equal(l_expected_item_type); select count(1) into l_remaining from ut3.ut_output_buffer_tmp where output_id = l_buffer.output_id; @@ -45,18 +57,18 @@ create or replace package body test_output_buffer is end; procedure test_waiting_for_data is - l_result varchar2(4000); + l_result clob; l_remaining integer; - l_expected varchar2(4000); + l_expected clob; l_buffer ut3.ut_output_buffer_base := ut3.ut_output_table_buffer(); l_start timestamp; l_duration interval day to second; begin --Act - l_expected := lpad('a text',4000,',a text'); + l_expected := 'a text'; l_buffer.send_line(l_expected); l_start := localtimestamp; - select * into l_result from table(l_buffer.get_lines(1,1)); + select text into l_result from table(l_buffer.get_lines(1,1)); l_duration := localtimestamp - l_start; ut.expect(l_result).to_equal(l_expected); @@ -64,7 +76,6 @@ create or replace package body test_output_buffer is select count(1) into l_remaining from ut3.ut_output_buffer_tmp where output_id = l_buffer.output_id; ut.expect(l_remaining).to_equal(0); - end; end test_output_buffer; diff --git a/test/core/test_output_buffer.pks b/test/core/test_output_buffer.pks index 1392b39ad..417b1ce09 100644 --- a/test/core/test_output_buffer.pks +++ b/test/core/test_output_buffer.pks @@ -4,7 +4,7 @@ create or replace package test_output_buffer is --%suitepath(utplsql.core) --%test(Receives a line from buffer table and deletes) - procedure test_recieve; + procedure test_receive; --%test(Does not send line if null text given) procedure test_doesnt_send_on_null_text; diff --git a/test/helpers/test_event_list.tps b/test/helpers/test_event_list.tps new file mode 100644 index 000000000..8abf71b13 --- /dev/null +++ b/test/helpers/test_event_list.tps @@ -0,0 +1,2 @@ +create or replace type test_event_list as table of test_event_object; +/ diff --git a/test/helpers/test_event_object.tps b/test/helpers/test_event_object.tps new file mode 100644 index 000000000..da458d2c2 --- /dev/null +++ b/test/helpers/test_event_object.tps @@ -0,0 +1,15 @@ +declare + l_exists integer; +begin + select count(1) into l_exists from user_types where type_name = 'TEST_EVENT_OBJECT'; + if l_exists > 0 then + execute immediate 'drop type test_event_object force'; + end if; +end; +/ + +create or replace type test_event_object as object ( + event_type varchar2(1000), + event_doc xmltype +) +/ \ No newline at end of file diff --git a/test/install_tests.sql b/test/install_tests.sql index af67534d7..96d2702f8 100644 --- a/test/install_tests.sql +++ b/test/install_tests.sql @@ -13,6 +13,8 @@ alter session set plsql_optimize_level=0; @@helpers/other_dummy_object.tps @@helpers/test_dummy_object.tps @@helpers/test_dummy_object_list.tps +@@helpers/test_event_object.tps +@@helpers/test_event_list.tps --Install tests @@core.pks @@ -42,6 +44,7 @@ set define off @@core/reporters/test_coverage/test_coveralls_reporter.pks @@core/reporters/test_coverage/test_cov_cobertura_reporter.pks @@core/reporters/test_junit_reporter.pks +@@core/reporters/test_realtime_reporter.pks set define on @@install_below_12_2.sql 'core/reporters/test_coverage/test_html_proftab_reporter.pks' set define off @@ -92,6 +95,7 @@ set define off @@core/reporters/test_coverage/test_coveralls_reporter.pkb @@core/reporters/test_coverage/test_cov_cobertura_reporter.pkb @@core/reporters/test_junit_reporter.pkb +@@core/reporters/test_realtime_reporter.pkb set define on @@install_below_12_2.sql 'core/reporters/test_coverage/test_html_proftab_reporter.pkb' set define off