NIHVIVO-76 Parse the suite output files to produce the summary stats.

This commit is contained in:
jeb228 2010-03-12 20:28:47 +00:00
parent 893a74f9d6
commit 588ddb9530
5 changed files with 567 additions and 1287 deletions

View file

@ -26,6 +26,32 @@ Parameters:
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
=end =end
require 'date'
require 'fileutils'
require File.expand_path('output_suite_parser', File.dirname(File.expand_path(__FILE__)))
require File.expand_path('output_summary_formatter', File.dirname(File.expand_path(__FILE__)))
class TestInfo
attr :test_name, true
attr :suite_name, true
attr :output_link, true
attr :status, true
end
class SuiteInfo
attr :name, true
attr :output_link, true
attr :status, true
end
class Status
GOOD = 0
BAD = 1
FAIR = 2
def self.html_class(status)
return %w{good bad fair}[status]
end
end
class OutputManager class OutputManager
# ------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------
@ -56,6 +82,221 @@ class OutputManager
end end
end end
# The CSS file for the output summary exists in the script directory.
# Copy it to the output directory.
#
def copy_css_file()
source = File.expand_path('output_summary.css', File.dirname(File.expand_path(__FILE__)))
dest = File.expand_path('summary.css', @output_directory)
FileUtils::copy_file(source, dest)
end
# Write the beginning of the summary file.
# This includes the HTML header, and the banner.
#
# f -- a file, already open for output.
#
def write_summary_header(f)
if @osp.overall_status == Status::BAD
overall_status = "FAILURE"
else
overall_status = "SUCCESS"
end
f.print <<END_HEADER
<html>
<head>
<title>Summary of Acceptance Tests #{@osp.start_time}</title>
<link rel="stylesheet" type="text/css" href="summary.css">
</head>
<body>
<div class="heading">
Acceptance test results: 3:45 p.m. March 10, 2010
<div class="#{Status::html_class(@osp.overall_status)} one-word">#{overall_status}</div>
</div>
END_HEADER
end
# Write the first section of the summary file. This section contains
# nested tables with fixed formats, containing overall stats.
#
# f -- a file, already open for output.
#
def write_summary_stats_section(f)
how_many_tests = @osp.tests.length
how_many_pass = 0
how_many_fail = 0
how_many_ignore = 0
@osp.tests.each do |t|
how_many_pass += 1 if t.status == Status::GOOD
how_many_ignore += 1 if t.status == Status::FAIR
how_many_fail += 1 if t.status == Status::BAD
end
f.print <<END_STATS
<div class="section">Summary</div>
<table class="summary" cellspacing="0">
<tr>
<td>
<table cellspacing="0">
<tr><td>Start time:</td><td>#{@osp.start_time}</td></tr>
<tr><td>End time</td><td>#{@osp.end_time}</td></tr>
<tr><td>Elapsed time</td><td>#{@osp.elapsed_time}</td></tr>
</table>
</td>
<td>
<table cellspacing="0">
<tr><td>Suites</td><td>#{@osp.suites.length}</td></tr>
<tr><td>Total tests</td><td>#{@osp.tests.length}</td></tr>
<tr class="good"><td>Passing tests</td><td>#{how_many_pass}</td></tr>
<tr class="bad"><td>Failing tests</td><td>#{how_many_fail}</td></tr>
<tr class="fair"><td>Ignored tests</td><td>#{how_many_ignore}</td></tr>
</table>
</td>
</tr>
</table>
END_STATS
end
# Write a table of failed tests to the summary file, with links
# to the detailed output for each test.
#
# f -- a file, already open for output.
#
def write_summary_failure_section(f)
fails = []
@osp.tests.each do |t|
fails << t if t.status == Status::BAD
end
f.print " <div class=section>Failing tests</div>\n\n <table cellspacing=\"0\">\n"
f.print " <tr><th>Suite name</th><th>Test name</th></tr>\n"
if fails.empty?
f.print " <tr>\n"
f.print " <td colspan=\"2\">No tests failed.</td>\n"
f.print " </tr>\n"
else
fails.each do |t|
f.print " <tr class=\"#{Status::html_class(t.status)}\">\n"
f.print " <td>#{t.suite_name}</td>\n"
f.print " <td><a href=\"#{t.output_link}\">#{t.test_name}</a></td>\n"
f.print " </tr>\n"
end
end
f.print " </table>\n\n"
end
# Write a table of ignored tests to the summary file, with links
# to the detailed output for each test.
#
# f -- a file, already open for output.
#
def write_summary_ignore_section(f)
ignores = []
@osp.tests.each do |t|
ignores << t if t.status == Status::FAIR
end
f.print " <div class=section>Ignored tests</div>\n\n <table cellspacing=\"0\">\n"
f.print " <tr><th>Suite name</th><th>Test name</th></tr>\n"
if ignores.empty?
f.print " <tr>\n"
f.print " <td colspan=\"2\">No tests ignored.</td>\n"
f.print " </tr>\n"
else
ignores.each do |t|
f.print " <tr class=\"#{Status::html_class(t.status)}\">\n"
f.print " <td>#{t.suite_name}</td>\n"
f.print " <td><a href=\"#{t.output_link}\">#{t.test_name}</a></td>\n"
f.print " </tr>\n"
end
end
f.print " </table>\n\n"
end
# Write a table of any error messages or warnings found in the log file.
#
# f -- a file, already open for output.
#
def write_error_messages_section(f)
f.print " <div class=section>Errors and warnings</div>\n"
f.print " <table cellspacing=\"0\">"
if @osp.errors.empty? && @osp.warnings.empty?
f.print " <tr><td colspan=\"2\">No errors or warnings</td></tr>"
else
@osp.errors.each() do |e|
f.print " <tr class=\"bad\"><td>ERROR</td><td>#{e}</td></tr>"
end
@osp.warnings.each() do |w|
f.print " <tr class=\"fair\"><td>WARN</td><td>#{w}</td></tr>"
end
end
f.print " </table>\n\n"
end
# Write a table of the suites to the summary file, with links
# to the detailed output for each suite.
#
# f -- a file, already open for output.
#
def write_summary_suites_section(f)
f.print " <div class=section>Suites</div>\n"
f.print " <table cellspacing=\"0\">\n"
@osp.suites.each() do |s|
f.print " <tr class=\"#{Status::html_class(s.status)}\">\n"
f.print " <td><a href=\"#{s.output_link}\">#{s.name}</a></td>\n"
f.print " </tr>\n"
end
f.print " </table>\n\n"
end
# Write a table of all tests to the summary file, with links
# to the detailed output for each test.
#
# f -- a file, already open for output.
#
def write_summary_all_tests_section(f)
f.print " <div class=section>All tests</div>\n\n <table cellspacing=\"0\">\n"
f.print " <tr><th>Suite name</th><th>Test name</th></tr>\n"
if @osp.tests.empty?
f.print " <tr>\n"
f.print " <td colspan=\"2\">No tests.</td>\n"
f.print " </tr>\n"
else
@osp.tests.each do |t|
f.print " <tr class=\"#{Status::html_class(t.status)}\">\n"
f.print " <td>#{t.suite_name}</td>\n"
f.print " <td><a href=\"#{t.output_link}\">#{t.test_name}</a></td>\n"
f.print " </tr>\n"
end
end
f.print " </table>\n\n"
end
# Copy the log to the summary file, and close the HTML tags.
#
# f -- a file, already open for output.
#
def write_summary_footer(f)
f.print " <div class=section>Log</div>\n <pre>\n"
File.open(@log_file) do |log|
FileUtils::copy_stream(log, f)
end
f.print " </pre>\n</body>\n</html>\n\n"
end
# ------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------
public public
@ -68,7 +309,10 @@ class OutputManager
sanity_checks_on_parameters() sanity_checks_on_parameters()
@log_file = File.expand_path("log_file.txt", @output_directory) @log_file = File.expand_path("log_file.txt", @output_directory)
FileUtils::remove_file(@log_file) if File.exist?(@log_file)
@output_summary_file = File.expand_path("summary.html", @output_directory) @output_summary_file = File.expand_path("summary.html", @output_directory)
FileUtils::remove_file(@output_summary_file) if File.exist?(@output_summary_file)
end end
# Write a message to the log file # Write a message to the log file
@ -83,6 +327,27 @@ class OutputManager
File.expand_path("#{suite_name}_output.html", @output_directory) File.expand_path("#{suite_name}_output.html", @output_directory)
end end
def ignore_test?(suite_name, test_name)
false
end
def summarize() def summarize()
@osp = OutputSuiteParser.new(self, @log_file)
@osp.parse()
copy_css_file()
File.open(@output_summary_file, File::CREAT | File::WRONLY) do |f|
osf = OutputSummaryFormatter.new(@osp, @log_file)
osf.format(f)
write_summary_header(f)
write_summary_stats_section(f)
write_error_messages_section(f)
write_summary_failure_section(f)
write_summary_ignore_section(f)
write_summary_suites_section(f)
write_summary_all_tests_section(f)
write_summary_footer(f)
end
end end
end end

View file

@ -0,0 +1,184 @@
=begin
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Parameters:
--------------------------------------------------------------------------------
=end
require 'date'
class OutputSuiteParser
attr :errors
attr :warnings
attr :start_time
attr :end_time
attr :elapsed_time
attr :suites
attr :tests
attr :overall_status
# ------------------------------------------------------------------------------------
private
# ------------------------------------------------------------------------------------
#
# Scan the log file for start time, end time, and the names of the suites that
# were run. Figure elapsed time also.
#
def parse_log_file()
@start_time = nil
@end_time = nil
@elapsed_time = "unknown"
@suite_names = []
File.open(@log_file) do |f|
f.each_line do |line|
md = %r{Start time: (.*)$}.match line
if md
@start_time = md[1]
end
md = %r{End time: (.*)$}.match line
if md
@end_time = md[1]
end
md = %r{Running suite (.*)$}.match line
if md
@suite_names << md[1]
end
md = %r{^ERROR\s*(.*)}.match line
if md
@errors << md[1]
end
md = %r{^WARN\s*(.*)}.match line
if md
@warnings << md[1]
end
end
end
if @start_time && @end_time
@elapsed_time = format_elapsed_time(@start_time, @end_time)
end
end
# Scan the output of a suite run.
#
def parse_suite_output(suite_name)
file_name = @output_manager.output_filename(suite_name)
s = SuiteInfo.new
s.name = suite_name
s.output_link = File.basename(file_name)
s.status = Status::GOOD
@suites << s
tests = []
begin
File.open(file_name) do |f|
f.each_line do |line|
md = %r{<tr class="\s*(\w+)"><td><a href="(#\w+)">([^<]*)</a></td></tr>}.match line
if md
t = TestInfo.new
t.test_name = md[3]
t.suite_name = s.name
t.output_link = s.output_link + md[2]
if md[1] == 'status_passed'
t.status = Status::GOOD
elsif @output_manager.ignore_test?(t.suite_name, t.test_name)
t.status = Status::FAIR
else
t.status = Status::BAD
end
tests << t
end
end
end
rescue Exception
log_error("Failed to parse output for suite '#{s.name}': #{$!}")
s.status = Status::BAD
end
s.status = get_worst_status(s.status, tests)
@tests = @tests.concat(tests)
end
# Look at the info from all of the suites and prepare summary info.
#
def collate_and_summarize()
status = Status::GOOD
status = Status::FAIR if !@warnings.empty?
status = Status::BAD if !@errors.empty?
@overall_status = get_worst_status(status, @suites)
end
# Find the worst status in an array of tests or suites
#
def get_worst_status(starting_status, statused)
worst = starting_status
statused.each do |s|
worst = s.status if s.status > worst
end
return worst
end
# Take two parsable time stamps and express the elapsed time as a string.
# Examples: 4h 37m 6.7s
# 15m 4s
# 55.6s
#
def format_elapsed_time(start_time_string, end_time_string)
start = Time.parse(start_time_string)
ender = Time.parse(end_time_string)
elapsed = ender - start
s = elapsed % 60
m = ((elapsed - s) / 60) % 60
h = (elapsed - s - (60 * m))/ 3600
elapsed_time = ""
elapsed_time << "#{h.to_i}h " if h > 0
elapsed_time << "#{m.to_i}m " if h > 0 || m > 0
elapsed_time << "#{s}s"
end
def log_error(message)
@output_manager.log("ERROR", message)
# By the time we get here, we've already scanned the log file for errors.
@errors << message
end
# ------------------------------------------------------------------------------------
public
# ------------------------------------------------------------------------------------
#
# Set up and get ready to process.
#
def initialize(output_manager, log_file)
@output_manager = output_manager
@log_file = log_file
@errors = []
@warnings = []
@suites = []
@tests = []
end
# Parse the output from each suite, and the log file, and munge it all together
# for easy access.
#
def parse()
parse_log_file()
@suite_names.each do |suite_name|
parse_suite_output(suite_name)
end
collate_and_summarize()
end
end

View file

@ -0,0 +1,65 @@
/*
Formats for the output summary from the acceptance tests.
*/
body {
background: rgb(95%, 95%, 95%);
font-family: sans-serif;
}
.heading {
border: groove;
background: white;
padding: 10px 20px 8px 20px;
margin-top: 50px;
font-size: large;
}
table {
border: thin double gray;
background: white;
}
td,th {
padding: 4px 12px 2px 12px;
}
th {
border-bottom: 1px solid black;
}
table.summary {
border: none;
background: inherit;
}
table.summary td {
padding-right: 30;
border: none;
vertical-align: top;
}
.section {
background: rgb(70%, 85%, 85%);
font-size: larger;
margin: 50px 0px 15px 0px;
padding: 4px 12px 2px 12px;
}
.good {
background: rgb(60%, 100%, 60%);
}
.bad {
background: rgb(100%, 60%, 60%);
}
.fair {
background: rgb(100%, 100%, 60%);
}
.one-word {
width: 20%;
text-align: center;
margin: 15px 0px 0px 0px;
border: 1px solid black;
}

View file

@ -0,0 +1,53 @@
=begin
--------------------------------------------------------------------------------
Stop the Vitro application, delete all MySQL tables from the Vitro database, and
start the application again.
--------------------------------------------------------------------------------
Parameters:
tomcat_stop_command
A "shell" command that will stop the Tomcat server.
tomcat_stop_delay
Number of seconds to wait after the tomcat_stop_command returns before
proceeding.
tomcat_start_command
A "shell" command that will start the Tomcat server.
tomcat_start_delay
Number of seconds to wait after the tomcat_start_command returns before
proceeding.
mysql_username
A user account that has authority to drop the Vitro database in MySQL.
mysql_password
The password for mysql_username.
database_name
The name of the Vitro database in MySQL.
--------------------------------------------------------------------------------
=end
class OutputSummaryFormatter
# ------------------------------------------------------------------------------------
private
# ------------------------------------------------------------------------------------
#
# Confirm that the output directory parameter is reasonable.
#
def sanity_checks_on_parameters()
end
# ------------------------------------------------------------------------------------
public
# ------------------------------------------------------------------------------------
# Set up and get ready to process.
#
def initialize(output_suite_parser, log_file)
@osp = output_suite_parser
@log_file = log_file
end
def format(f)
end
end

File diff suppressed because it is too large Load diff