1create or replace package ut_utils authid definer is
2  /*
3  utPLSQL - Version 3
4  Copyright 2016 - 2017 utPLSQL Project
5
6  Licensed under the Apache License, Version 2.0 (the "License"):
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17  */
18
19  /**
20   * Common utilities and constants used throughout utPLSQL framework
21   *
22   */
23
24  gc_version                 constant varchar2(50) := 'v3.0.4.1461-develop';
25
26  /* Constants: Event names */
27  gc_run                     constant varchar2(12) := 'run';
28  gc_suite                   constant varchar2(12) := 'suite';
29  gc_before_all              constant varchar2(12) := 'before_all';
30  gc_before_each             constant varchar2(12) := 'before_each';
31  gc_before_test             constant varchar2(12) := 'before_test';
32  gc_test                    constant varchar2(12) := 'test';
33  gc_test_execute            constant varchar2(12) := 'test_execute';
34  gc_after_test              constant varchar2(10) := 'after_test';
35  gc_after_each              constant varchar2(12) := 'after_each';
36  gc_after_all               constant varchar2(12) := 'after_all';
37
38  /* Constants: Test Results */
39  tr_disabled                constant number(1) := 0; -- test/suite was disabled
40  tr_success                 constant number(1) := 1; -- test passed
41  tr_failure                 constant number(1) := 2; -- one or more expectations failed
42  tr_error                   constant number(1) := 3; -- exception was raised
43
44  tr_disabled_char           constant varchar2(8) := 'Disabled'; -- test/suite was disabled
45  tr_success_char            constant varchar2(7) := 'Success'; -- test passed
46  tr_failure_char            constant varchar2(7) := 'Failure'; -- one or more expectations failed
47  tr_error_char              constant varchar2(5) := 'Error'; -- exception was raised
48
49  /*
50    Constants: Rollback type for ut_test_object
51  */
52  gc_rollback_auto           constant number(1) := 0; -- rollback after each test and suite
53  gc_rollback_manual         constant number(1) := 1; -- leave transaction control manual
54  --gc_rollback_on_error       constant number(1) := 2; -- rollback tests only on error
55
56  ex_unsupported_rollback_type exception;
57  gc_unsupported_rollback_type constant pls_integer := -20200;
58  pragma exception_init(ex_unsupported_rollback_type, -20200);
59
60  ex_path_list_is_empty exception;
61  gc_path_list_is_empty constant pls_integer := -20201;
62  pragma exception_init(ex_path_list_is_empty, -20201);
63
64  ex_invalid_path_format exception;
65  gc_invalid_path_format constant pls_integer := -20202;
66  pragma exception_init(ex_invalid_path_format, -20202);
67
68  ex_suite_package_not_found exception;
69  gc_suite_package_not_found constant pls_integer := -20204;
70  pragma exception_init(ex_suite_package_not_found, -20204);
71
72  -- Reporting event time not supported
73  ex_invalid_rep_event_time exception;
74  gc_invalid_rep_event_time constant pls_integer := -20210;
75  pragma exception_init(ex_invalid_rep_event_time, -20210);
76
77  -- Reporting event name not supported
78  ex_invalid_rep_event_name exception;
79  gc_invalid_rep_event_name constant pls_integer := -20211;
80  pragma exception_init(ex_invalid_rep_event_name, -20211);
81
82  -- Any of tests failed
83  ex_some_tests_failed exception;
84  gc_some_tests_failed constant pls_integer := -20213;
85  pragma exception_init(ex_some_tests_failed, -20213);
86
87  -- Any of tests failed
88  ex_invalid_version_no exception;
89  gc_invalid_version_no constant pls_integer := -20214;
90  pragma exception_init(ex_invalid_version_no, -20214);
91
92  gc_max_storage_varchar2_len constant integer := 4000;
93  gc_max_output_string_length constant integer := 4000;
94  gc_max_input_string_length  constant integer := gc_max_output_string_length - 2; --we need to remove 2 chars for quotes around string
95  gc_more_data_string         constant varchar2(5) := '[...]';
96  gc_overflow_substr_len      constant integer := gc_max_input_string_length - length(gc_more_data_string);
97  gc_number_format            constant varchar2(100) := 'TM9';
98  gc_date_format              constant varchar2(100) := 'yyyy-mm-dd"T"hh24:mi:ss';
99  gc_timestamp_format         constant varchar2(100) := 'yyyy-mm-dd"T"hh24:mi:ssxff';
100  gc_timestamp_tz_format      constant varchar2(100) := 'yyyy-mm-dd"T"hh24:mi:ssxff tzh:tzm';
101  gc_null_string              constant varchar2(4) := 'NULL';
102
103  type t_version is record(
104    major  natural,
105    minor  natural,
106    bugfix natural,
107    build  natural
108  );
109
110
111  /**
112   * Converts test results into strings
113   *
114   * @param a_test_result numeric representation of test result
115   *
116   * @return a string representation of a test_result.
117   */
118  function test_result_to_char(a_test_result integer) return varchar2;
119
120  function to_test_result(a_test boolean) return integer;
121
122  /**
123   * Generates a unique name for a savepoint
124   * Uses sys_guid, as timestamp gives only miliseconds on Windows and is not unique
125   * Issue: #506 for details on the implementation approach
126   */
127  function gen_savepoint_name return varchar2;
128
129  procedure debug_log(a_message varchar2);
130
131  procedure debug_log(a_message clob);
132
133  function to_string(a_value varchar2, a_qoute_char varchar2 := '''') return varchar2;
134
135  function to_string(a_value clob, a_qoute_char varchar2 := '''') return varchar2;
136
137  function to_string(a_value blob, a_qoute_char varchar2 := '''') return varchar2;
138
139  function to_string(a_value boolean) return varchar2;
140
141  function to_string(a_value number) return varchar2;
142
143  function to_string(a_value date) return varchar2;
144
145  function to_string(a_value timestamp_unconstrained) return varchar2;
146
147  function to_string(a_value timestamp_tz_unconstrained) return varchar2;
148
149  function to_string(a_value timestamp_ltz_unconstrained) return varchar2;
150
151  function to_string(a_value yminterval_unconstrained) return varchar2;
152
153  function to_string(a_value dsinterval_unconstrained) return varchar2;
154
155  function boolean_to_int(a_value boolean) return integer;
156
157  function int_to_boolean(a_value integer) return boolean;
158
159  /**
160   * Validates passed value against supported rollback types
161   */
162  procedure validate_rollback_type(a_rollback_type number);
163
164
165  /**
166   *
167   * Splits a given string into table of string by delimiter.
168   * The delimiter gets removed.
169   * If null passed as any of the parameters, empty table is returned.
170   * If no occurence of a_delimiter found in a_text then text is returned as a single row of the table.
171   * If no text between delimiters found then an empty row is returned, example:
172   *   string_to_table( 'a,,b', ',' ) gives table ut_varchar2_list( 'a', null, 'b' );
173   *
174   * @param a_string                 the text to be split.
175   * @param a_delimiter              the delimiter character or string
176   * @param a_skip_leading_delimiter determines if the leading delimiter should be ignored, used by clob_to_table
177   *
178   * @return table of varchar2 values
179   */
180  function string_to_table(a_string varchar2, a_delimiter varchar2:= chr(10), a_skip_leading_delimiter varchar2 := 'N') return ut_varchar2_list;
181
182  /**
183   * Splits a given string into table of string by delimiter.
184   * Default value of a_max_amount is 8191 because of code can contains multibyte character.
185   * The delimiter gets removed.
186   * If null passed as any of the parameters, empty table is returned.
187   * If split text is longer than a_max_amount it gets split into pieces of a_max_amount.
188   * If no text between delimiters found then an empty row is returned, example:
189   *   string_to_table( 'a,,b', ',' ) gives table ut_varchar2_list( 'a', null, 'b' );
190   *
191   * @param a_clob       the text to be split.
192   * @param a_delimiter  the delimiter character or string (default chr(10) )
193   * @param a_max_amount the maximum length of returned string (default 8191)
194   * @return table of varchar2 values
195   */
196  function clob_to_table(a_clob clob, a_max_amount integer := 8191, a_delimiter varchar2:= chr(10)) return ut_varchar2_list;
197
198  function table_to_clob(a_text_table ut_varchar2_list, a_delimiter varchar2:= chr(10)) return clob;
199
200  /**
201   * Returns time difference in seconds (with miliseconds) between given timestamps
202   */
203  function time_diff(a_start_time timestamp with time zone, a_end_time timestamp with time zone) return number;
204
205  /**
206   * Returns a text indented with spaces except the first line.
207   */
208  function indent_lines(a_text varchar2, a_indent_size integer := 4, a_include_first_line boolean := false) return varchar2;
209
210
211  /**
212   * Returns a list of object that are part of utPLSQL framework
213   */
214  function get_utplsql_objects_list return ut_object_names;
215
216  /**
217   * Append a line to the end of ut_varchar2_lst
218   */
219  procedure append_to_varchar2_list(a_list in out nocopy ut_varchar2_list, a_line varchar2);
220
221  procedure append_to_clob(a_src_clob in out nocopy clob, a_new_data clob);
222  procedure append_to_clob(a_src_clob in out nocopy clob, a_new_data varchar2);
223
224  function convert_collection(a_collection ut_varchar2_list) return ut_varchar2_rows;
225
226  /**
227   * Set session's action and module using dbms_application_info
228   */
229  procedure set_action(a_text in varchar2);
230
231  /**
232   * Set session's client info using dbms_application_info
233   */
234  procedure set_client_info(a_text in varchar2);
235
236  function to_xpath(a_list varchar2, a_ancestors varchar2 := '/*/') return varchar2;
237
238  function to_xpath(a_list ut_varchar2_list, a_ancestors varchar2 := '/*/') return varchar2;
239
240  procedure cleanup_temp_tables;
241
242  /**
243   * Converts version string into version record
244   *
245   * @param    a_version_no string representation of version in format vX.X.X.X where X is a positive integer
246   * @return   t_version    record with up to four positive numbers containing version
247   * @throws   20214        if passed version string is not matching version pattern
248   */
249  function to_version(a_version_no varchar2) return t_version;
250
251
252  /**
253  * Saves data from dbms_output buffer into a global temporary table (cache)
254  *   used to store dbms_output buffer captured before the run
255  *
256  */
257  procedure save_dbms_output_to_cache;
258
259  /**
260  * Reads data from global temporary table (cache) abd puts it back into dbms_output
261  *   used to recover dbms_output buffer data after a run is complete
262  *
263  */
264  procedure read_cache_to_dbms_output;
265
266
267  /**
268   * Function is used to reference to utPLSQL owned objects in dynamic sql statements executed from packages with invoker rights
269   *
270   * @return the name of the utPSQL schema owner
271   */
272  function ut_owner return varchar2;
273
274
275  /**
276   * Used in dynamic sql select statements to maintain balance between
277   *   number of hard-parses and optimiser accurancy for cardinality of collections
278   *
279   *
280   * @return 3, for inputs of: 1-9; 33 for input of 10 - 99; 333 for (100 - 999)
281   */
282  function scale_cardinality(a_cardinality natural) return natural;
283
284end ut_utils;
285/
286
287create or replace package body ut_suite_builder is
288  /*
289  utPLSQL - Version 3
290  Copyright 2016 - 2017 utPLSQL Project
291
292  Licensed under the Apache License, Version 2.0 (the "License"):
293  you may not use this file except in compliance with the License.
294  You may obtain a copy of the License at
295
296      http://www.apache.org/licenses/LICENSE-2.0
297
298  Unless required by applicable law or agreed to in writing, software
299  distributed under the License is distributed on an "AS IS" BASIS,
300  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
301  See the License for the specific language governing permissions and
302  limitations under the License.
303  */
304
305  ------------------
306
307  function create_suite(a_object ut_annotated_object) return ut_logical_suite is
308    l_is_suite              boolean := false;
309    l_is_test               boolean := false;
310    l_suite_disabled        boolean := false;
311    l_test_disabled         boolean := false;
312    l_suite_items           ut_suite_items := ut_suite_items();
313    l_suite_name            varchar2(4000);
314
315    l_default_setup_proc    varchar2(250 char);
316    l_default_teardown_proc varchar2(250 char);
317    l_suite_setup_proc      varchar2(250 char);
318    l_suite_teardown_proc   varchar2(250 char);
319    l_suite_path            varchar2(4000 char);
320
321    l_proc_name             varchar2(250 char);
322
323    l_suite                 ut_logical_suite;
324    l_test                  ut_test;
325
326    l_suite_rollback        integer;
327
328    l_beforetest_procedure  varchar2(250 char);
329    l_aftertest_procedure   varchar2(250 char);
330    l_rollback_type         integer;
331    l_displayname           varchar2(4000);
332    function is_last_annotation_for_proc(a_annotations ut_annotations, a_index binary_integer) return boolean is
333    begin
334      return a_index = a_annotations.count or a_annotations(a_index).subobject_name != nvl(a_annotations(a_index+1).subobject_name, ' ');
335    end;
336  begin
337    l_suite_rollback := ut_utils.gc_rollback_auto;
338    for i in 1 .. a_object.annotations.count loop
339
340      if a_object.annotations(i).subobject_name is null then
341
342        if a_object.annotations(i).name in ('suite','displayname') then
343          l_suite_name := a_object.annotations(i).text;
344          if a_object.annotations(i).name = 'suite' then
345            l_is_suite := true;
346          end if;
347        elsif a_object.annotations(i).name = 'disabled' then
348          l_suite_disabled := true;
349        elsif a_object.annotations(i).name = 'suitepath' and  a_object.annotations(i).text is not null then
350          l_suite_path := a_object.annotations(i).text || '.' || lower(a_object.object_name);
351        elsif a_object.annotations(i).name = 'rollback' then
352          if lower(a_object.annotations(i).text) = 'manual' then
353            l_suite_rollback := ut_utils.gc_rollback_manual;
354          else
355            l_suite_rollback := ut_utils.gc_rollback_auto;
356          end if;
357        end if;
358
359      elsif l_is_suite then
360
361        l_proc_name := a_object.annotations(i).subobject_name;
362
363        if a_object.annotations(i).name = 'beforeeach' and l_default_setup_proc is null then
364          l_default_setup_proc := l_proc_name;
365        elsif a_object.annotations(i).name = 'aftereach' and l_default_teardown_proc is null then
366          l_default_teardown_proc := l_proc_name;
367        elsif a_object.annotations(i).name = 'beforeall' and l_suite_setup_proc is null then
368          l_suite_setup_proc := l_proc_name;
369        elsif a_object.annotations(i).name = 'afterall' and l_suite_teardown_proc is null then
370          l_suite_teardown_proc := l_proc_name;
371
372
373        elsif a_object.annotations(i).name = 'disabled' then
374          l_test_disabled := true;
375        elsif a_object.annotations(i).name = 'beforetest' then
376          l_beforetest_procedure := a_object.annotations(i).text;
377        elsif a_object.annotations(i).name = 'aftertest' then
378          l_aftertest_procedure := a_object.annotations(i).text;
379        elsif a_object.annotations(i).name in ('displayname','test') then
380          l_displayname := a_object.annotations(i).text;
381          if a_object.annotations(i).name = 'test' then
382            l_is_test := true;
383          end if;
384        elsif a_object.annotations(i).name = 'rollback' then
385          if lower(a_object.annotations(i).text) = 'manual' then
386            l_rollback_type := ut_utils.gc_rollback_manual;
387          elsif lower(a_object.annotations(i).text) = 'auto' then
388            l_rollback_type := ut_utils.gc_rollback_auto;
389          end if;
390        end if;
391
392        if l_is_test and is_last_annotation_for_proc(a_object.annotations, i) then
393          l_suite_items.extend;
394          l_suite_items(l_suite_items.last) :=
395            ut_test(a_object_owner          => a_object.object_owner
396                   ,a_object_name           => a_object.object_name
397                   ,a_name                  => l_proc_name
398                   ,a_description           => l_displayname
399                   ,a_rollback_type         => coalesce(l_rollback_type, l_suite_rollback)
400                   ,a_disabled_flag         => l_test_disabled
401                   ,a_before_test_proc_name => l_beforetest_procedure
402                   ,a_after_test_proc_name  => l_aftertest_procedure);
403
404          l_is_test := false;
405          l_test_disabled := false;
406          l_aftertest_procedure  := null;
407          l_beforetest_procedure := null;
408          l_rollback_type        := null;
409        end if;
410
411      end if;
412    end loop;
413
414    if l_is_suite then
415      l_suite := ut_suite (
416          a_object_owner          => a_object.object_owner,
417          a_object_name           => a_object.object_name,
418          a_name                  => a_object.object_name, --this could be different for sub-suite (context)
419          a_path                  => l_suite_path,  --a patch for this suite (excluding the package name of current suite)
420          a_description           => l_suite_name,
421          a_rollback_type         => l_suite_rollback,
422          a_disabled_flag         => l_suite_disabled,
423          a_before_all_proc_name  => l_suite_setup_proc,
424          a_after_all_proc_name   => l_suite_teardown_proc
425      );
426      for i in 1 .. l_suite_items.count loop
427        l_test := treat(l_suite_items(i) as ut_test);
428        l_test.set_beforeeach(l_default_setup_proc);
429        l_test.set_aftereach(l_default_teardown_proc);
430        l_test.path := l_suite.path  || '.' ||  l_test.name;
431        l_suite.add_item(l_test);
432      end loop;
433    end if;
434
435    return l_suite;
436
437  end create_suite;
438
439  function build_suites_hierarchy(a_suites_by_path tt_schema_suites) return tt_schema_suites is
440    l_result            tt_schema_suites;
441    l_suite_path        varchar2(4000 char);
442    l_parent_path       varchar2(4000 char);
443    l_name              varchar2(4000 char);
444    l_suites_by_path    tt_schema_suites;
445  begin
446    l_suites_by_path := a_suites_by_path;
447    --were iterating in reverse order of the index by path table
448    -- so the first paths will be the leafs of hierarchy and next will their parents
449    l_suite_path  := l_suites_by_path.last;
450    ut_utils.debug_log('Input suites to process = '||l_suites_by_path.count);
451
452    while l_suite_path is not null loop
453      l_parent_path := substr( l_suite_path, 1, instr(l_suite_path,'.',-1)-1);
454      ut_utils.debug_log('Processing l_suite_path = "'||l_suite_path||'", l_parent_path = "'||l_parent_path||'"');
455      --no parent => I'm a root element
456      if l_parent_path is null then
457        ut_utils.debug_log('  suite "'||l_suite_path||'" is a root element - adding to return list.');
458        l_result(l_suite_path) := l_suites_by_path(l_suite_path);
459      -- not a root suite - need to add it to a parent suite
460      else
461        --parent does not exist and needs to be added
462        if not l_suites_by_path.exists(l_parent_path) then
463          l_name  := substr( l_parent_path, instr(l_parent_path,'.',-1)+1);
464          ut_utils.debug_log('  Parent suite "'||l_parent_path||'" not found in the list - Adding suite "'||l_name||'"');
465          l_suites_by_path(l_parent_path) :=
466            ut_logical_suite(
467              a_object_owner => l_suites_by_path(l_suite_path).object_owner,
468              a_object_name => l_name, a_name => l_name, a_path => l_parent_path
469            );
470        else
471          ut_utils.debug_log('  Parent suite "'||l_parent_path||'" found in list of suites');
472        end if;
473        ut_utils.debug_log('  adding suite "'||l_suite_path||'" to "'||l_parent_path||'" items');
474        l_suites_by_path(l_parent_path).add_item( l_suites_by_path(l_suite_path) );
475      end if;
476      l_suite_path := l_suites_by_path.prior(l_suite_path);
477    end loop;
478    ut_utils.debug_log(l_result.count||' root suites created.');
479    return l_result;
480  end;
481
482  function build_suites(a_annotated_objects sys_refcursor) return t_schema_suites_info is
483    l_suite             ut_logical_suite;
484    l_annotated_objects ut_annotated_objects;
485    l_all_suites        tt_schema_suites;
486    l_result            t_schema_suites_info;
487  begin
488    fetch a_annotated_objects bulk collect into l_annotated_objects;
489    close a_annotated_objects;
490
491    for i in 1 .. l_annotated_objects.count loop
492      l_suite := create_suite(l_annotated_objects(i));
493      if l_suite is not null then
494        l_all_suites(l_suite.path) := l_suite;
495        l_result.suite_paths(l_suite.object_name) := l_suite.path;
496      end if;
497    end loop;
498
499    --build hierarchical structure of the suite
500    -- Restructure single-dimension list into hierarchy of suites by the value of %suitepath attribute value
501    l_result.schema_suites := build_suites_hierarchy(l_all_suites);
502
503    return l_result;
504  end;
505
506  function build_schema_suites(a_owner_name varchar2) return t_schema_suites_info is
507    l_annotations_cursor sys_refcursor;
508  begin
509    -- form the single-dimension list of suites constructed from parsed packages
510    open l_annotations_cursor for
511      q'[select value(x)
512          from table(
513            ]'||ut_utils.ut_owner||q'[.ut_annotation_manager.get_annotated_objects(:a_owner_name, 'PACKAGE')
514          )x ]'
515      using a_owner_name;
516
517    return build_suites(l_annotations_cursor);
518  end;
519
520end ut_suite_builder;
521/
522/*http://example.com.*/
523/* comment /* comment */
524comment
525*/
526