note description: "Generates HTML pages describing test statistics" copyright: "Copyright (c) 2005, Andreas Leitner and others" license: "Eiffel Forum License v2 (see forum.txt)" date: "$Date$" revision: "$Revision$" class AUT_HTML_STATISTICS_GENERATOR inherit AUT_STATISTICS_GENERATOR redefine make end AUT_SHARED_PATHNAMES export {NONE} all end AUT_SHARED_FILE_SYSTEM_ROUTINES export {NONE} all end KL_SHARED_STREAMS export {NONE} all end KL_SHARED_FILE_SYSTEM export {NONE} all end REFACTORING_HELPER AUT_TASK INTERNAL_COMPILER_STRING_EXPORTER create make feature {NONE} -- Initialization make (an_output_dirname: like output_dirname; a_system: like system; a_classes_under_test: like classes_under_test) -- Create new html generator. do Precursor (an_output_dirname, a_system, a_classes_under_test) create test_case_printer.make_null (system) end feature -- Access absolute_index_filename: STRING -- Absolute filename of main entry page do Result := file_system.pathname (output_dirname, "index.html") end feature {NONE} -- Access repository: detachable AUT_TEST_CASE_RESULT_REPOSITORY -- Result repository for which documentation is currently generated cursor: detachable DS_LINEAR_CURSOR [CLASS_C] -- Cursor pointing to current class being processed by `step' state: NATURAL_8 -- Current state of `step' class_state: NATURAL_8 = 1 scope_state: NATURAL_8 = 2 manual_state: NATURAL_8 = 3 other_state: NATURAL_8 = 4 -- Valid states for `state' counter: INTEGER -- Counter used by `step' is_first: BOOLEAN -- Boolean flag used by `step' feature -- Status report has_next_step: BOOLEAN -- do Result := cursor /= Void end feature -- Status setting start -- -- -- Note: `start' and `step' together do the same as `generate'. local l_repo: like repository l_cursor: like cursor do l_repo := repository if l_repo /= Void then current_id := 0 file_system.recursive_create_directory (output_dirname) file_system.recursive_create_directory (file_system.pathname (output_dirname, "image")) copy_static_files create tree_content_file.make (file_system.pathname (output_dirname, "treeContent.js")) tree_content_file.open_write generate_tree_content_file_header generate_summary_page (l_repo) generate_header_page if not tree_content_file.is_open_write then has_fatal_error := True else l_cursor := l_repo.classes.new_cursor l_cursor.start cursor := l_cursor state := class_state end end end step -- -- -- Note: `start' and `step' together do the same as `generate'. local l_cursor: like cursor l_repo: like repository do l_cursor := cursor l_repo := repository if l_cursor = Void or l_cursor.after then -- Actions performed at the end of the stage currently set in `state' if state = other_state then tree_content_file.put_line ("])") else inspect state when class_state then tree_content_file.put_string ("ClassesInScope.addChildren([") state := scope_state when scope_state then tree_content_file.put_line ("])") tree_content_file.put_string ("ManualTests.addChildren([") state := manual_state when manual_state then tree_content_file.put_line ("])") tree_content_file.put_string ("Others.addChildren([") state := other_state end l_cursor := repository.classes.new_cursor cursor := l_cursor counter := 1 is_first := True end else inspect state when class_state then generate_class (l_cursor.item, l_repo) when scope_state then if is_class_in_test_scope (l_cursor.item) then if is_first then is_first := False else tree_content_file.put_string (", ") end tree_content_file.put_string ("class_") tree_content_file.put_string (l_cursor.item.name_in_upper) end when manual_state then if is_manual_test_class (l_cursor.item) then if is_first then is_first := False else tree_content_file.put_string (", ") end tree_content_file.put_string ("class_") tree_content_file.put_string (l_cursor.item.name_in_upper) end when other_state then if not is_class_in_test_scope (l_cursor.item) and not is_manual_test_class (l_cursor.item) then if is_first then is_first := False else tree_content_file.put_string (", ") end tree_content_file.put_string ("class_") tree_content_file.put_string (l_cursor.item.name_in_upper) end end counter := counter + 1 end if has_fatal_error or (l_cursor = Void or else l_cursor.after) then cancel else l_cursor.forth end end cancel -- local l_cursor: like cursor do generate_tree_content_file_footer tree_content_file.close repository := Void l_cursor := cursor if l_cursor /= Void then l_cursor.go_after cursor := Void end end feature -- Element change set_repository (a_repository: like repository) -- Set `repository' to `a_repository'. require not_started: not has_next_step do repository := a_repository end generate (a_repository: AUT_TEST_CASE_RESULT_REPOSITORY) -- Generate HTML pages describing the results from `a_repository'. local cs: DS_LINEAR_CURSOR [CLASS_C] i: INTEGER l_first: BOOLEAN do current_id := 0 file_system.recursive_create_directory (output_dirname) file_system.recursive_create_directory (file_system.pathname (output_dirname, "image")) copy_static_files create tree_content_file.make (file_system.pathname (output_dirname, "treeContent.js")) tree_content_file.open_write generate_tree_content_file_header generate_summary_page (a_repository) generate_header_page if not tree_content_file.is_open_write then has_fatal_error := True else from cs := a_repository.classes.new_cursor cs.start until cs.off loop generate_class (cs.item, a_repository) cs.forth end tree_content_file.put_string ("ClassesInScope.addChildren([") from cs := a_repository.classes.new_cursor cs.start i := 1 l_first := True until cs.off loop if is_class_in_test_scope (cs.item) then if l_first then l_first := False else tree_content_file.put_string (", ") end tree_content_file.put_string ("class_") tree_content_file.put_string (cs.item.name_in_upper) end cs.forth i := i + 1 end tree_content_file.put_line ("])") tree_content_file.put_string ("ManualTests.addChildren([") from cs := a_repository.classes.new_cursor cs.start i := 1 l_first := True until cs.off loop if is_manual_test_class (cs.item) then if l_first then l_first := False else tree_content_file.put_string (", ") end tree_content_file.put_string ("class_") tree_content_file.put_string (cs.item.name_in_upper) end cs.forth i := i + 1 end tree_content_file.put_line ("])") tree_content_file.put_string ("Others.addChildren([") from cs := a_repository.classes.new_cursor cs.start i := 1 l_first := True until cs.off loop if not is_class_in_test_scope (cs.item) and not is_manual_test_class (cs.item) then if l_first then l_first := False else tree_content_file.put_string (", ") end tree_content_file.put_string ("class_") tree_content_file.put_string (cs.item.name_in_upper) end cs.forth i := i + 1 end tree_content_file.put_line ("])") end generate_tree_content_file_footer tree_content_file.close end feature {NONE} -- HTML Generation copy_static_files -- Copy static files from test studio directory to html directory. do file_system_routines.copy_recursive (pathnames.image_dirname.name, file_system.pathname (output_dirname, "image")) file_system_routines.copy_recursive (pathnames.misc_html_dirname.name, output_dirname) end generate_summary_page (a_repository: AUT_TEST_CASE_RESULT_REPOSITORY) -- Generate main summary page. require a_repository_not_void: a_repository /= Void local file: KL_TEXT_OUTPUT_FILE_32 set: AUT_TEST_CASE_RESULT_SET do create file.make (file_system.pathname (output_dirname, "summary.html")) file.open_write if file.is_open_write then set := a_repository.results file.put_string (template_expander.expand_from_array (summary_page_header_template, <>)) generate_summary (set, file) file.put_string (summary_page_footer_template) file.close else has_fatal_error := True end end generate_header_page -- Generate header page. local file: KL_TEXT_OUTPUT_FILE_32 do create file.make (file_system.pathname (output_dirname, "header.html")) file.open_write if file.is_open_write then file.put_string (template_expander.expand_from_array (header_page_template, <>)) file.close else has_fatal_error := True end end generate_class (a_class: CLASS_C; a_repository: AUT_TEST_CASE_RESULT_REPOSITORY) -- Generate HTML pages describing the results from `a_repository' of class `a_class'. require a_repository_not_void: a_repository /= Void a_class_not_void: a_class /= Void tree_content_file_not_void: tree_content_file /= Void tree_content_file_open_write: tree_content_file.is_open_write local file: KL_TEXT_OUTPUT_FILE_32 absolute_filename: STRING filename: STRING set: AUT_TEST_CASE_RESULT_SET image_filename: STRING child_id_list: DS_ARRAYED_LIST [INTEGER] cs: DS_ARRAYED_LIST_CURSOR [INTEGER] l_feat_table: FEATURE_TABLE feature_i: FEATURE_I do filename := class_summary_file_name (a_class) absolute_filename := file_system.pathname (output_dirname, filename) set := a_repository.results_by_class (a_class) create image_filename.make (100) image_filename.append_string ("image/class_") if set.is_bad_response then image_filename.append_string ("bad_response") elseif set.is_fail then image_filename.append_string ("fail") elseif set.is_invalid then image_filename.append_string ("invalid") elseif set.is_pass then image_filename.append_string ("pass") elseif set.is_untested then image_filename.append_string ("untested") else check dead_end: False end end image_filename.append_string (".gif") tree_content_file.put_string ("class_") tree_content_file.put_string (a_class.name) tree_content_file.put_string (" = gFld (%"") tree_content_file.put_string (a_class.name) tree_content_file.put_string ("%", %"") tree_content_file.put_string (filename) tree_content_file.put_line ("%")") tree_content_file.put_string ("class_") tree_content_file.put_string (a_class.name) tree_content_file.put_string (".iconSrc = %"") tree_content_file.put_string (image_filename) tree_content_file.put_character ('"') tree_content_file.put_new_line tree_content_file.put_string ("class_") tree_content_file.put_string (a_class.name) tree_content_file.put_string (".iconSrcClosed = %"") tree_content_file.put_string (image_filename) tree_content_file.put_character ('"') tree_content_file.put_new_line create file.make (absolute_filename) file.open_write if file.is_open_write then file.put_string (template_expander.expand_from_array (class_summary_header_template, <>)) generate_summary (set, file) file.put_string (class_summary_footer_template) file.close create child_id_list.make (a_class.feature_table.count) l_feat_table := a_class.feature_table -- if not is_manual_test_class (a_class) then -- from -- l_feat_table.start -- until -- l_feat_table.after -- loop -- feature_i := l_feat_table.item_for_iteration -- if feature_i /= Void and then (feature_i.is_attribute or feature_i.is_function) and then not feature_i.is_prefix and then not feature_i.is_infix then -- generate_feature (a_class, feature_i, a_repository, child_id_list) -- end -- l_feat_table.forth -- end -- end from l_feat_table.start until l_feat_table.after loop feature_i := l_feat_table.item_for_iteration if feature_i /= Void and then not (feature_i.is_attribute or feature_i.is_function) and then not feature_i.is_prefix and then not feature_i.is_infix then generate_feature (a_class, feature_i, a_repository, child_id_list) end l_feat_table.forth end tree_content_file.put_string ("class_") tree_content_file.put_string (a_class.name) tree_content_file.put_string (".addChildren ([") from cs := child_id_list.new_cursor cs.start until cs.off loop tree_content_file.put_string ("id_") tree_content_file.put_integer (cs.item) if not cs.is_last then tree_content_file.put_string (", ") end cs.forth end tree_content_file.put_line ("])") else has_fatal_error := True end end generate_feature (a_class: CLASS_C; a_feature: FEATURE_I; a_repository: AUT_TEST_CASE_RESULT_REPOSITORY; a_id_list: DS_ARRAYED_LIST [INTEGER]) -- Generate HTML pages describing the results from `a_repository' of class `a_class'. -- This routine also generates the corresponding tree node entry and puts the id for the node into `a_id_list'. require a_repository_not_void: a_repository /= Void a_class_not_void: a_class /= Void a_feature_not_void: a_feature /= Void a_feature_not_prefix_and_not_infix: not a_feature.is_prefix and not a_feature.is_prefix tree_content_file_not_void: tree_content_file /= Void tree_content_file_open_write: tree_content_file.is_open_write a_id_list_not_void: a_id_list /= Void local file: KL_TEXT_OUTPUT_FILE_32 set: AUT_TEST_CASE_RESULT_SET absolute_filename: STRING filename: STRING cs: DS_LINEAR_CURSOR [AUT_TEST_CASE_RESULT] image_filename: STRING l_count: NATURAL do set := a_repository.results_by_feature_and_class (a_feature, a_class) if a_feature.written_class.name.is_case_insensitive_equal ("ANY") and then (set.is_pass or set.is_untested) then -- Ignore features from ANY that don't show any interesting results else filename := feature_summary_file_name (a_class, a_feature) absolute_filename := file_system.pathname (output_dirname, filename) test_case_result_sorter.sort (set.list) create image_filename.make (20) image_filename.append_string ("image/feature_") if set.is_bad_response then image_filename.append_string ("bad_response") elseif set.is_fail then image_filename.append_string ("fail") elseif set.is_invalid then image_filename.append_string ("invalid") elseif set.is_pass then image_filename.append_string ("pass") elseif set.is_untested then image_filename.append_string ("untested") else check dead_end: False end end image_filename.append_string (".gif');") a_id_list.force_last (current_id) tree_content_file.put_string ("id_") tree_content_file.put_integer (current_id) tree_content_file.put_string (" = gFld (%"") tree_content_file.put_string (a_feature.feature_name) tree_content_file.put_string ("%", %"") tree_content_file.put_string (filename) tree_content_file.put_line ("%")") tree_content_file.put_string ("id_") tree_content_file.put_integer (current_id) tree_content_file.put_string (".iconSrc = %"") tree_content_file.put_string (image_filename) tree_content_file.put_character ('"') tree_content_file.put_new_line tree_content_file.put_string ("id_") tree_content_file.put_integer (current_id) tree_content_file.put_string (".iconSrcClosed = %"") tree_content_file.put_string (image_filename) tree_content_file.put_character ('"') tree_content_file.put_new_line create file.make (absolute_filename) file.open_write test_case_printer.set_output_stream (file) if file.is_open_write then file.put_string (template_expander.expand_from_array (feature_summary_header_template, <>)) generate_summary (set, file) file.put_line ("

Failed Test Cases:

") from l_count := 1 cs := set.list.new_cursor cs.start until --| to limit the size of the HTML statistics we only generate 10 --| results per tested routine cs.off or l_count > 10 loop if cs.item.is_fail then generate_test_case_result (cs.item, file) l_count := l_count + 1 end cs.forth end -- file.put_line ("

Bad response Test Cases:

") -- from -- cs := set.list.new_cursor -- cs.start -- until -- cs.off -- loop -- if cs.item.is_bad_response then -- generate_test_case_result (cs.item, file) -- end -- cs.forth -- end -- file.put_line ("

Passed Test Cases:

") -- from -- cs := set.list.new_cursor -- cs.start -- until -- cs.off -- loop -- if cs.item.is_pass then -- generate_test_case_result (cs.item, file) -- end -- cs.forth -- end -- file.put_line ("

Invalid Test Cases:

") -- from -- cs := set.list.new_cursor -- cs.start -- until -- cs.off -- loop -- if cs.item.is_invalid then -- generate_test_case_result (cs.item, file) -- end -- cs.forth -- end file.put_string (feature_summary_footer_template) test_case_printer.set_output_stream (null_output_stream) file.close consume_id else has_fatal_error := True end end end generate_test_case_result (a_result: AUT_TEST_CASE_RESULT; a_stream: KI_TEXT_OUTPUT_STREAM) -- Print test case results in `a_set' to `a_stream'. require a_result_not_void: a_result /= Void a_stream_not_void: a_stream /= Void a_stream_open_write: a_stream.is_open_write local explain_link: STRING status: STRING response_printer: AUT_RESPONSE_LOG_PRINTER do a_stream.put_string ("

Test Case: ") if a_result.is_pass then explain_link := "test_case_pass.html" status := "pass" elseif a_result.is_fail then explain_link := "test_case_fail.html" status := "fail" elseif a_result.is_invalid then explain_link := "test_case_invalid.html" status := "invalid" elseif a_result.is_bad_response then explain_link := "test_case_bad_response.html" status := "bad response" else check dead_end: False end end a_stream.put_line (template_expander.expand_from_array (explain_link_template, <>)) a_stream.put_line ("

") a_stream.put_line ("
Witness:
") a_stream.put_line ("
")
			test_case_printer.print_test_case (a_result.witness.request_list, a_result.witness.used_vars)
			a_stream.put_line ("
") a_stream.put_line ("Result:
") a_stream.put_line ("
")
			create response_printer.make (a_stream)
			a_result.witness.item (a_result.witness.count).response.process (response_printer)
			a_stream.put_line ("
") a_stream.put_line ("
") end generate_summary (a_set: AUT_TEST_CASE_RESULT_SET; a_stream: KI_TEXT_OUTPUT_STREAM) -- Generate summary for set `a_set' into `a_stream'. require a_set_not_void: a_set /= Void a_stream_not_void: a_stream /= Void a_stream_open_write: a_stream.is_open_write local explain_link: STRING status: STRING do a_stream.put_string ("

Status: ") if a_set.is_bad_response then explain_link := "test_case_set_bad_response.html" status := "bad response" elseif a_set.is_fail then explain_link := "test_case_set_fail.html" status := "fail" elseif a_set.is_invalid then explain_link := "test_case_set_invalid.html" status := "invalid" elseif a_set.is_pass then explain_link := "test_case_set_pass.html" status := "pass" elseif a_set.is_untested then explain_link := "test_case_set_untested.html" status := "untested" else check dead_end: False end end a_stream.put_line (template_expander.expand_from_array (explain_link_template, <>)) a_stream.put_line ("

") a_stream.put_string (template_expander.expand_from_array (summary_table_template, <>)) end generate_tree_content_file_header -- Generate header for `tree_file'. require tree_content_file_not_void: tree_content_file /= Void tree_content_file_open_write: tree_content_file.is_open_write do tree_content_file.put_line ("USETEXTLINKS = 1") tree_content_file.put_line ("STARTALLOPEN = 0") tree_content_file.put_line ("HIGHLIGHT = 1") tree_content_file.put_line ("PRESERVESTATE = 1") tree_content_file.put_line ("GLOBALTARGET=%"R%"") tree_content_file.put_line ("ICONPATH=%"image/%"") tree_content_file.put_line ("foldersTree = gFld(%"" + system.name + "%", %"summary.html%")") tree_content_file.put_line ("foldersTree.iconSrc = %"image/folderopen.gif%"") tree_content_file.put_line ("foldersTree.iconSrcClosed = %"image/folder.gif%"") tree_content_file.put_line ("ClassesInScope = gFld(%"Classes in Scope%", %"summary.html%")") tree_content_file.put_line ("ClassesInScope.iconSrc = %"image/folderopen.gif%"") tree_content_file.put_line ("ClassesInScope.iconSrcClosed = %"image/folder.gif%"") tree_content_file.put_line ("ManualTests = gFld(%"Manual unit tests%", %"summary.html%")") tree_content_file.put_line ("ManualTests.iconSrc = %"image/folderopen.gif%"") tree_content_file.put_line ("ManualTests.iconSrcClosed = %"image/folder.gif%"") tree_content_file.put_line ("Others = gFld(%"Other classes%", %"summary.html%")") tree_content_file.put_line ("Others.iconSrc = %"image/folderopen.gif%"") tree_content_file.put_line ("Others.iconSrcClosed = %"image/folder.gif%"") end generate_tree_content_file_footer -- Generate header for `tree_file'. require tree_content_file_not_void: tree_content_file /= Void tree_content_file_open_write: tree_content_file.is_open_write do -- Disabling the menu entry for manual tests since they are currently not taken into account (Arno 05/20/2009) --tree_content_file.put_line ("foldersTree.addChildren([ClassesInScope, ManualTests, Others])") tree_content_file.put_line ("foldersTree.addChildren([ClassesInScope, Others])") end feature {NONE} -- File handles tree_content_file: KL_TEXT_OUTPUT_FILE_32 -- File that contains script to build Javascript tree feature {NONE} -- Implementation class_summary_file_name (a_class: CLASS_C): STRING -- File name for class sumamry file for class `a_class' require a_class_not_void: a_class /= Void do create Result.make (a_class.name.count + class_summary_file_postfix.count) Result.append_string (a_class.name) Result.to_lower Result.append_string (class_summary_file_postfix) ensure name_not_void: Result /= Void name_not_empty: Result.count > 0 end feature_summary_file_name (a_class: CLASS_C; a_feature: FEATURE_I): STRING -- File name for feature sumamry file for feature `a_feature' of class `a_class' require a_class_not_void: a_class /= Void a_feature_not_void: a_feature /= Void do create Result.make (a_class.name.count + 1 + a_feature.feature_name.count + feature_summary_file_postfix.count) Result.append_string (a_class.name) Result.to_lower Result.append_character ('-') Result.append_string (a_feature.feature_name) Result.append_string (feature_summary_file_postfix) ensure name_not_void: Result /= Void name_not_empty: Result.count > 0 end consume_id -- Mark the id stored in `current_id' as used and provide a new one. do current_id := current_id + 1 end current_id: INTEGER -- Next id to use test_case_printer: AUT_TEST_CASE_PRINTER -- Test case printer feature {NONE} -- Implementation constants class_summary_file_postfix: STRING = "_class_summary.html" feature_summary_file_postfix: STRING = "_feature_summary.html" class_summary_header_template: STRING = "[ Class Summary

${1}

]" class_summary_footer_template: STRING = "[
]" feature_summary_header_template: STRING = "[ Feature Summary

${1}.${2}

Below you will find all executed test cases. Note that AutoTest tries, for those test cases that prove the existence of a bug, to add a minimized version too.

]" feature_summary_footer_template: STRING = "[
]" summary_page_header_template: STRING = "[ Summary Page

System '${1}'

]" summary_page_footer_template: STRING = "[
]" summary_table_template: STRING = "[

Test Case Summary

Total: ${1}
Unique: ${2}
Bad Response: ${3}
Fail: ${4}
Invalid: ${5}
Pass: ${6}
]" header_page_template: STRING = "[ AutoTest statistics for system '${1}'
se logo

AutoTest statistics for system '${1}'

We Kick Bugs!

   
]" class_has_error_template: STRING = "[
  • The class has syntax or type errors.
]" explain_link_template: STRING = "[ ${2} ]" template_expander: AUT_TEMPLATE_EXPANDER -- Shared template expander once create Result.make end invariant test_case_printer_not_void: test_case_printer /= Void note copyright: "Copyright (c) 1984-2013, Eiffel Software" license: "GPL version 2 (see http://www.eiffel.com/licensing/gpl.txt)" licensing_options: "http://www.eiffel.com/licensing" copying: "[ This file is part of Eiffel Software's Eiffel Development Environment. Eiffel Software's Eiffel Development Environment is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License (available at the URL listed under "license" above). Eiffel Software's Eiffel Development Environment is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Eiffel Software's Eiffel Development Environment; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ]" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end