# frozen_string_literal: true require "timeout" require ::File.expand_path('file_asset', File.dirname(__FILE__)) require ::File.expand_path('pretty_formatter', File.dirname(__FILE__)) require ::File.expand_path('report_table', File.dirname(__FILE__)) # require ::File.expand_path('configuration', File.dirname(__FILE__)) module Cornucopia module Util class ReportBuilder @@current_report = nil @@on_close_blocks = [] MAX_OLD_FOLDERS = 5 class TestDataHolder end class << self def current_report(folder_name = nil, parent_folder = nil) if (@@current_report && ((parent_folder && @@current_report.instance_variable_get(:@parent_folder_name) != parent_folder) || (folder_name && @@current_report.instance_variable_get(:@base_folder_name) != folder_name))) @@current_report.close @@current_report = nil end folder_name ||= Cornucopia::Util::Configuration.base_folder parent_folder ||= Cornucopia::Util::Configuration.base_folder @@current_report ||= Cornucopia::Util::ReportBuilder.new(folder_name, parent_folder) end def new_report(folder_name = nil, parent_folder = nil) if (@@current_report) @@current_report.close @@current_report = nil end @@current_report = Cornucopia::Util::ReportBuilder.new(folder_name, parent_folder) end def escape_string(value) value = value.to_s value = value + "" if value.frozen? "".html_safe + value.force_encoding("UTF-8") end def format_code_refs(value) safe_text = Cornucopia::Util::ReportBuilder.escape_string(value) safe_text = safe_text.gsub(/(#{Cornucopia::Util::ReportBuilder.root_folder}|\.\/|(?=(?:^features|^spec)\/))([^\:\r\n &]*(?: *\: *[0-9]+)?)/, "\\1 \\2 ").html_safe safe_text end def pretty_array(array, format_sub_items = true) if (array.is_a?(Array)) array.map do |value| # This is not expected to be called on an array of arrays, # block this and just convert sub-arrays to strings. format_value = value format_value = value.to_s if value.is_a?(Array) if format_sub_items Cornucopia::Util::ReportBuilder.pretty_format(format_value, in_pretty_print: true).rstrip else # "".html_safe + format_value format_value end end.join("\n").html_safe else Cornucopia::Util::ReportBuilder.pretty_format(array) end end # I've seen some objects with pretty_inspect get stuck in an infinite loop (or close to it), # so they take literally hours to print. This function uses Timeout to prevent this. # either the class prints quickly, or we kill it and try something else. # # If the something else doesn't work, we just give up... def pretty_object(value, options = {}) timed_out = false return_value = nil begin begin timeout_length = Cornucopia::Util::Configuration.print_timeout_min if Object.const_defined?("Capybara") default_time = ::Capybara.respond_to?(:default_max_wait_time) ? ::Capybara.default_max_wait_time : ::Capybara.default_wait_time timeout_length = [timeout_length, default_time].max end if Object.const_defined?("Rails") timeout_length = [timeout_length, 30].max if Rails.env.development? end Timeout::timeout(timeout_length) do if value.is_a?(String) return_value = value elsif value.is_a?(Array) return_value = Cornucopia::Util::ReportBuilder.pretty_array(value, !options[:in_pretty_print]) elsif value.respond_to?(:pretty_inspect) return_value = value.pretty_inspect else return_value = value.to_s end end rescue Timeout::Error timed_out = true end rescue Exception end # If it timed out or threw an exception, try .to_s # That may also timeout or throw an exception, and if it does, give up. if timed_out || !return_value begin Timeout::timeout(timeout_length) do return_value = value.to_s end rescue Timeout::Error return_value = "Timed out rendering" rescue => error return_value = "Rendering error => #{error.to_s}\n#{error.backtrace.join("\n")}" end end return_value end def pretty_format(value, options = {}) pretty_text = value pretty_text = Cornucopia::Util::ReportBuilder.pretty_object(pretty_text, options) pretty_text = Cornucopia::Util::ReportBuilder.escape_string(pretty_text) pretty_text = Cornucopia::Util::PrettyFormatter.format_string(pretty_text) pretty_text = Cornucopia::Util::ReportBuilder.format_code_refs(pretty_text) pretty_text end def build_index_section_item(path_name) item_path = "".html_safe item_path << "
No Errors to report
] write_file.write "\n" end end if self == @@current_report @@current_report = nil end exceptions.each do |exception| raise exception end end def report_folder_name unless @report_folder_name @report_folder_name = File.join(index_folder_name, "#{@base_folder_name}/") backup_report_folder delete_old_folders end @report_folder_name end def report_test_folder_name unless @report_test_folder_name @test_number += 1 @report_test_folder_name = File.join(report_folder_name, "test_#{@test_number}") end @report_test_folder_name end def index_folder_name unless @index_folder_name @index_folder_name = File.join(Cornucopia::Util::ReportBuilder.root_folder, "#{@parent_folder_name}/") FileUtils.mkdir_p @index_folder_name end @index_folder_name end def backup_report_folder if Dir.exist?(@report_folder_name) if File.exist?(File.join(@report_folder_name, "index.html")) update_time = File.ctime(File.join(@report_folder_name, "index.html")) else update_time = File.ctime(@report_folder_name) end # ensure the name is unique... new_sub_dir = File.join(index_folder_name, "#{@base_folder_name}_#{update_time.strftime("%Y_%m_%d_%H_%M_%S")}").to_s index = 0 while Dir.exist?(new_sub_dir) if new_sub_dir[-1 * "_alt_#{index}".length..-1] == "_alt_#{index}" new_sub_dir = new_sub_dir[0..-1 * "_alt_#{index}".length - 1] end index += 1 new_sub_dir += "_alt_#{index}" end FileUtils.mv @report_folder_name, new_sub_dir end end def delete_old_folders old_directories = Dir[File.join(index_folder_name, "#{@base_folder_name}_*")]. map { |dir| File.directory?(dir) ? dir : nil }.compact if Array.wrap(old_directories).length > MAX_OLD_FOLDERS old_directories.each_with_index do |dir, index| break if index >= old_directories.length - MAX_OLD_FOLDERS FileUtils.rm_rf dir end end end def rebuild_index_page index_folder = index_folder_name FileUtils.mkdir_p index_folder_name FileUtils.rm_rf index_contents_page_name FileAsset.asset("index_base.html").create_file(File.join(index_folder, "index.html")) FileAsset.asset("index_contents.html").add_file(File.join(index_folder, "report_contents.html")) FileAsset.asset("cornucopia.css").add_file(File.join(index_folder, "cornucopia.css")) index_file = "".html_safe if File.exist?(File.join(Cornucopia::Util::ReportBuilder.root_folder, "coverage/index.html")) index_file << Cornucopia::Util::ReportBuilder.build_index_section("Coverage", ["../coverage/index.html"]) end last_folder = nil group_items = [] Dir[File.join(@index_folder_name, "*")].sort.each do |directory_item| if File.directory?(directory_item) && File.exist?(File.join(directory_item, "index.html")) directory_item = directory_item[@index_folder_name.to_s.length..-1] if last_folder if directory_item =~ /^#{last_folder}_/ group_items << File.join(directory_item, "index.html") else unless group_items.empty? index_file << Cornucopia::Util::ReportBuilder.build_index_section( Cornucopia::Util::ReportBuilder.folder_name_to_section_name(last_folder), group_items) end last_folder = directory_item group_items = [File.join(last_folder, "index.html")] end else last_folder = directory_item group_items = [File.join(last_folder, "index.html")] end end end unless group_items.empty? index_file << Cornucopia::Util::ReportBuilder.build_index_section( Cornucopia::Util::ReportBuilder.folder_name_to_section_name(last_folder), group_items) end File.open(index_contents_page_name, "a:UTF-8") do |write_file| write_file << index_file end end def rebuild_report_holder_page initialize_report_files report_folder = report_folder_name FileUtils.mkdir_p report_folder_name FileUtils.rm_rf report_base_page_name report_holder_body = FileAsset.asset("report_holder.html").body FileAsset.asset("report.js").add_file(File.join(report_folder, "report.js")) FileAsset.asset("cornucopia.css").add_file(File.join(report_folder, "cornucopia.css")) File.open(File.join(report_folder, "index.html"), "w+") do |write_file| write_file << report_holder_body % { report_list: @report_body, report_title: @report_title } end end def report_base_page_name File.join(report_folder_name, "index.html") end def report_contents_page_name File.join(report_folder_name, "report_contents.html") end def report_test_base_page_name File.join(report_test_folder_name, "index.html") end def report_test_contents_page_name File.join(report_test_folder_name, "report_contents.html") end def index_base_page_name File.join(index_folder_name, "index.html") end def index_contents_page_name File.join(index_folder_name, "report_contents.html") end def initialize_report_files support_folder_name = report_folder_name FileUtils.mkdir_p @report_folder_name unless File.exist?(report_base_page_name) # use a different base index file. FileAsset.asset("report_holder.html").add_file(File.join(support_folder_name, "index.html")) rebuild_index_page end FileAsset.asset("report.js").add_file(File.join(support_folder_name, "report.js")) FileAsset.asset("cornucopia.css").add_file(File.join(support_folder_name, "cornucopia.css")) end def initialize_basic_report_files support_folder_name = report_folder_name FileUtils.mkdir_p @report_folder_name unless File.exist?(report_base_page_name) # use a different base index file. FileAsset.asset("report_base.html").add_file(File.join(support_folder_name, "index.html")) rebuild_index_page end FileAsset.asset("report_contents.html").add_file(File.join(support_folder_name, "report_contents.html")) FileAsset.asset("collapse.gif").add_file(File.join(support_folder_name, "collapse.gif")) FileAsset.asset("expand.gif").add_file(File.join(support_folder_name, "expand.gif")) FileAsset.asset("more_info.js").add_file(File.join(support_folder_name, "more_info.js")) FileAsset.asset("cornucopia.css").add_file(File.join(support_folder_name, "cornucopia.css")) end def body_list_item(name, path) body_list_item = "#{Cornucopia::Util::ReportBuilder.escape_string(section_text)}
\n". dup.force_encoding("UTF-8") end block.yield self ensure open_report_test_contents_file do |write_file| write_file.write "