summaryrefslogtreecommitdiffstats
path: root/report-generators
diff options
context:
space:
mode:
authorJoe Thornber <thornber@redhat.com>2010-07-20 14:38:44 +0000
committerJoe Thornber <thornber@redhat.com>2010-07-20 14:38:44 +0000
commit1033d120404c0e41ced5f36361e412ac03a3461f (patch)
tree8776b2d0770ad642176c9e079987e621251f1e23 /report-generators
parent60f425d1b3508d71883ac1a4501f6ab7f3e32b08 (diff)
downloadlvm2-1033d120404c0e41ced5f36361e412ac03a3461f.tar.gz
lvm2-1033d120404c0e41ced5f36361e412ac03a3461f.tar.xz
lvm2-1033d120404c0e41ced5f36361e412ac03a3461f.zip
Report generators for unit tests and memory checks. Configure with
--enable-testing.
Diffstat (limited to 'report-generators')
-rw-r--r--report-generators/lib/log.rb30
-rw-r--r--report-generators/lib/report_templates.rb28
-rw-r--r--report-generators/lib/reports.rb48
-rw-r--r--report-generators/lib/schedule_file.rb46
-rw-r--r--report-generators/lib/string-store.rb32
-rw-r--r--report-generators/memcheck.rb76
-rw-r--r--report-generators/templates/boiler_plate.rhtml25
-rw-r--r--report-generators/templates/index.rhtml17
-rw-r--r--report-generators/templates/memcheck.rhtml30
-rw-r--r--report-generators/templates/unit_detail.rhtml37
-rw-r--r--report-generators/templates/unit_test.rhtml23
-rw-r--r--report-generators/test/example.schedule4
-rw-r--r--report-generators/test/strings/more_strings/test3.txt1
-rw-r--r--report-generators/test/strings/test1.txt1
-rw-r--r--report-generators/test/strings/test23
-rw-r--r--report-generators/test/tc_log.rb26
-rw-r--r--report-generators/test/tc_schedule_file.rb28
-rw-r--r--report-generators/test/tc_string_store.rb19
-rw-r--r--report-generators/test/ts.rb3
-rw-r--r--report-generators/title_page.rb32
-rw-r--r--report-generators/unit_test.rb46
21 files changed, 555 insertions, 0 deletions
diff --git a/report-generators/lib/log.rb b/report-generators/lib/log.rb
new file mode 100644
index 00000000..c98b1839
--- /dev/null
+++ b/report-generators/lib/log.rb
@@ -0,0 +1,30 @@
+# Merely wraps the logger library with a bit of standard policy.
+require 'logger'
+
+module Log
+ $log = Logger.new(STDERR)
+
+ def init(io_)
+ $log = Logger.new(io_)
+ end
+end
+
+def fatal(*args)
+ $log.fatal(*args)
+end
+
+def error(*args)
+ $log.error(*args)
+end
+
+def info(*args)
+ $log.info(*args)
+end
+
+def warning(*args)
+ $log.warn(*args)
+end
+
+def debug(*args)
+ $log.debug(*args)
+end
diff --git a/report-generators/lib/report_templates.rb b/report-generators/lib/report_templates.rb
new file mode 100644
index 00000000..83e3acbf
--- /dev/null
+++ b/report-generators/lib/report_templates.rb
@@ -0,0 +1,28 @@
+# Policy for the location of report templates
+require 'string-store'
+
+class TemplateStringStore < StringStore
+ def initialize()
+ super(['report-generators/templates'])
+ end
+end
+
+module ReportTemplates
+ def generate_report(report, bs, dest_path = nil)
+ include Reports
+ reports = ReportRegister.new
+ template_store = TemplateStringStore.new
+ report = reports.get_report(report)
+ erb = ERB.new(template_store.lookup(report.template))
+ body = erb.result(bs)
+ title = report.short_desc
+
+ erb = ERB.new(template_store.lookup("boiler_plate.rhtml"))
+ txt = erb.result(binding)
+
+ dest_path = dest_path.nil? ? report.path : dest_path
+ dest_path.open("w") do |out|
+ out.puts txt
+ end
+ end
+end
diff --git a/report-generators/lib/reports.rb b/report-generators/lib/reports.rb
new file mode 100644
index 00000000..57333324
--- /dev/null
+++ b/report-generators/lib/reports.rb
@@ -0,0 +1,48 @@
+# Data about the various reports we support
+require 'log'
+require 'pathname'
+
+module Reports
+ Report = Struct.new(:short_desc, :desc, :path, :template)
+
+ class ReportRegister
+ attr_reader :reports
+
+ private
+ def add_report(sym, *args)
+ @reports[sym] = Report.new(*args)
+ end
+
+ public
+ def initialize()
+ @reports = Hash.new
+
+ add_report(:unit_test,
+ "Unit Tests",
+ "unit tests",
+ Pathname.new("reports/unit.html"),
+ Pathname.new("unit_test.rhtml"))
+
+ add_report(:memcheck,
+ "Memory Tests",
+ "unit tests with valgrind memory checking",
+ Pathname.new("reports/memcheck.html"),
+ Pathname.new("memcheck.rhtml"))
+
+ add_report(:unit_detail,
+ "Unit Test Detail",
+ "unit test detail",
+ Pathname.new("reports/unit_detail.html"), # FIXME replace this with a lambda
+ Pathname.new("unit_detail.rhtml"))
+ end
+
+ def get_report(sym)
+ raise RuntimeError, "unknown report '#{sym}'" unless @reports.member?(sym)
+ @reports[sym]
+ end
+
+ def each(&block)
+ @reports.each(&block)
+ end
+ end
+end
diff --git a/report-generators/lib/schedule_file.rb b/report-generators/lib/schedule_file.rb
new file mode 100644
index 00000000..1e164f45
--- /dev/null
+++ b/report-generators/lib/schedule_file.rb
@@ -0,0 +1,46 @@
+# Parses the simple colon delimited test schedule files.
+
+ScheduledTest = Struct.new(:desc, :command_line, :status, :output)
+
+class Schedule
+ attr_reader :dir, :schedules
+
+ def initialize(dir, ss)
+ @dir = dir
+ @schedules = ss
+ end
+
+ def run
+ Dir::chdir(@dir.to_s) do
+ @schedules.each do |s|
+ reader, writer = IO.pipe
+ print "#{s.desc} ... "
+ pid = spawn(s.command_line, [ STDERR, STDOUT ] => writer)
+ writer.close
+ _, s.status = Process::waitpid2(pid)
+ puts (s.status.success? ? "pass" : "fail")
+ s.output = reader.read
+ end
+ end
+ end
+
+ def self.read(dir, io)
+ ss = Array.new
+
+ io.readlines.each do |line|
+ case line.strip
+ when /^\#.*/
+ next
+
+ when /([^:]+):(.*)/
+ ss << ScheduledTest.new($1.strip, $2.strip)
+
+ else
+ raise RuntimeError, "badly formatted schedule line"
+ end
+ end
+
+ Schedule.new(dir, ss)
+ end
+end
+
diff --git a/report-generators/lib/string-store.rb b/report-generators/lib/string-store.rb
new file mode 100644
index 00000000..36d819a9
--- /dev/null
+++ b/report-generators/lib/string-store.rb
@@ -0,0 +1,32 @@
+# Provides a simple way of accessing the contents of files by a symbol
+# name. Useful for erb templates.
+
+require 'pathname'
+
+class StringStore
+ attr_accessor :path
+
+ def initialize(p)
+ @paths = p.nil? ? Array.new : p # FIXME: do we need to copy p ?
+ end
+
+ def lookup(sym)
+ files = expansions(sym)
+
+ @paths.each do |p|
+ files.each do |f|
+ pn = Pathname.new("#{p}/#{f}")
+ if pn.file?
+ return pn.read
+ end
+ end
+ end
+
+ raise RuntimeError, "unknown string entry: #{sym}"
+ end
+
+ private
+ def expansions(sym)
+ ["#{sym}", "#{sym}.txt"]
+ end
+end
diff --git a/report-generators/memcheck.rb b/report-generators/memcheck.rb
new file mode 100644
index 00000000..fcc8927e
--- /dev/null
+++ b/report-generators/memcheck.rb
@@ -0,0 +1,76 @@
+# Reads the schedule files given on the command line. Runs them and
+# generates the reports.
+
+# FIXME: a lot of duplication with unit_test.rb
+
+require 'schedule_file'
+require 'pathname'
+require 'reports'
+require 'erb'
+require 'report_templates'
+
+include ReportTemplates
+
+schedules = ARGV.map do |f|
+ p = Pathname.new(f)
+ Schedule.read(p.dirname, p)
+end
+
+total_passed = 0
+total_failed = 0
+
+# We need to make sure the lvm shared libs are in the LD_LIBRARY_PATH
+ENV['LD_LIBRARY_PATH'] = `pwd`.strip + "/libdm:" + (ENV['LD_LIBRARY_PATH'] || '')
+
+ENV['TEST_TOOL'] = "valgrind --leak-check=full --show-reachable=yes"
+
+schedules.each do |s|
+ s.run
+
+ s.schedules.each do |t|
+ if t.status.success?
+ total_passed += 1
+ else
+ total_failed += 1
+ end
+ end
+end
+
+def mangle(txt)
+ txt.gsub(/\s+/, '_')
+end
+
+MemcheckStats = Struct.new(:definitely_lost, :indirectly_lost, :possibly_lost, :reachable)
+
+def format(bytes, blocks)
+ "#{bytes} bytes, #{blocks} blocks"
+end
+
+# Examines the output for details of leaks
+def extract_stats(t)
+ d = i = p = r = '-'
+
+ t.output.split("\n").each do |l|
+ case l
+ when /==\d+== definitely lost: ([0-9,]+) bytes in ([0-9,]+) blocks/
+ d = format($1, $2)
+ when /==\d+== indirectly lost: ([0-9,]+) bytes in ([0-9,]+) blocks/
+ i = format($1, $2)
+ when /==\d+== possibly lost: ([0-9,]+) bytes in ([0-9,]+) blocks/
+ p = format($1, $2)
+ when /==\d+== still reachable: ([0-9,]+) bytes in ([0-9,]+) blocks/
+ r = format($1, $2)
+ end
+ end
+
+ MemcheckStats.new(d, i, p, r)
+end
+
+generate_report(:memcheck, binding)
+
+# now we generate a detail report for each schedule
+schedules.each do |s|
+ s.schedules.each do |t|
+ generate_report(:unit_detail, binding, Pathname.new("reports/memcheck_#{mangle(t.desc)}.html"))
+ end
+end
diff --git a/report-generators/templates/boiler_plate.rhtml b/report-generators/templates/boiler_plate.rhtml
new file mode 100644
index 00000000..23f01cbd
--- /dev/null
+++ b/report-generators/templates/boiler_plate.rhtml
@@ -0,0 +1,25 @@
+<html>
+<head>
+<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
+<title><%= title %></title>
+<link title="Style" type="text/css" rel="stylesheet" href="stylesheet.css">
+</head>
+
+<body>
+<div id="banner">
+<h2><%= title %></h2>
+</div>
+<div id="main">
+ <div id="controls">
+ <table>
+ <tr><td><a href="index.html">Generation times</a></td></tr>
+ <tr><td><a href="unit.html">Unit tests</a></td></tr>
+ <tr><td><a href="memcheck.html">Memory tests</a></td></tr>
+ </table>
+ </div>
+
+ <div id="body">
+ <%= body %>
+ </div>
+</div>
+</body>
diff --git a/report-generators/templates/index.rhtml b/report-generators/templates/index.rhtml
new file mode 100644
index 00000000..6d72081f
--- /dev/null
+++ b/report-generators/templates/index.rhtml
@@ -0,0 +1,17 @@
+<table width="95%" cellspacing="2" cellpadding="5" border="0" class="stripes">
+<tr><th>Report</th><th>Generation time</th></tr>
+<% [:unit_test, :memcheck].each do |sym| %>
+<% r = reports.get_report(sym) %>
+<tr>
+ <td>
+ <% if r.path.file? %>
+ <a href="<%= r.path.to_s.gsub(/^reports\//, '') %>"><%= r.short_desc %></a>
+ <% else %>
+ <%= r.short_desc %>
+ <% end %>
+ </td>
+ <td><%= safe_mtime(r) %></td>
+</tr>
+<% end %>
+</table>
+
diff --git a/report-generators/templates/memcheck.rhtml b/report-generators/templates/memcheck.rhtml
new file mode 100644
index 00000000..75872ed4
--- /dev/null
+++ b/report-generators/templates/memcheck.rhtml
@@ -0,0 +1,30 @@
+<table width="95%" cellspacing="2" cellpadding="5" border="0" class="stripes">
+ <tr><th>Tests passed</th><th>Tests failed</th></tr>
+ <tr><td class="pass"><%= total_passed %></td><td <%= total_failed == 0 ? "" : "class=\"fail\""%>><%= total_failed %></td></tr>
+</table>
+
+<% schedules.each do |s| %>
+<h3><%= s.dir.sub('./unit-tests/', '') %></h3>
+<table width="95%" cellspacing="2" cellpadding="5" border="0" class="stripes">
+<tr><th>Test</th><th>Result</th><th>Definitely lost</th><th>indirectly lost</th><th>possibly lost</th><th>still reachable</th><tr>
+
+<% s.schedules.each do |t| %>
+<tr>
+ <td>
+ <a href="memcheck_<%= mangle(t.desc) %>.html"><%= t.desc %></a>
+ </td>
+ <% if t.status.success? %>
+ <td class="pass">pass</td>
+ <% else %>
+ <td class="fail">fail</td>
+ <% end %>
+
+ <% stats = extract_stats(t) %>
+ <td><%= stats.definitely_lost %></td>
+ <td><%= stats.indirectly_lost %></td>
+ <td><%= stats.possibly_lost %></td>
+ <td><%= stats.reachable %></td>
+</tr>
+<% end %>
+</table>
+<% end %>
diff --git a/report-generators/templates/unit_detail.rhtml b/report-generators/templates/unit_detail.rhtml
new file mode 100644
index 00000000..5324f07c
--- /dev/null
+++ b/report-generators/templates/unit_detail.rhtml
@@ -0,0 +1,37 @@
+<table width="95%" cellspacing="2" cellpadding="5" border="0" class="stripes">
+<tr><th>Test</th><th>Result</th></tr>
+<tr>
+ <td>
+ <%= t.desc %>
+ </td>
+ <% if t.status.success? %>
+ <td class="pass">pass</td>
+ <% else %>
+ <td class="fail">fail</td>
+ <% end %>
+</tr>
+</table>
+
+<table width="95%" cellspacing="2" cellpadding="5" border="0" class="stripes">
+<tr><th>Command line</th></tr>
+<tr>
+ <td>
+ <pre>
+<%= t.command_line %>
+ </pre>
+ </td>
+</tr>
+</table>
+
+
+<table width="95%" cellspacing="2" cellpadding="5" border="0" class="stripes">
+<tr><th>Output</th></tr>
+<tr>
+ <td>
+ <pre>
+<%= t.output %>
+ </pre>
+ </td>
+</tr>
+</table>
+
diff --git a/report-generators/templates/unit_test.rhtml b/report-generators/templates/unit_test.rhtml
new file mode 100644
index 00000000..3137abdd
--- /dev/null
+++ b/report-generators/templates/unit_test.rhtml
@@ -0,0 +1,23 @@
+<table width="95%" cellspacing="2" cellpadding="5" border="0" class="stripes">
+ <tr><th>Tests passed</th><th>Tests failed</th></tr>
+ <tr><td class="pass"><%= total_passed %></td><td <%= total_failed == 0 ? "" : "class=\"fail\""%>><%= total_failed %></td></tr>
+</table>
+
+<% schedules.each do |s| %>
+<h3><%= s.dir.sub('./unit-tests/', '') %></h3>
+<table width="95%" cellspacing="2" cellpadding="5" border="0" class="stripes">
+<tr><th>Test</th><th>Result</th></tr>
+<% s.schedules.each do |t| %>
+<tr>
+ <td>
+ <a href="detail_<%= mangle(t.desc) %>.html"><%= t.desc %></a>
+ </td>
+ <% if t.status.success? %>
+ <td class="pass">pass</td>
+ <% else %>
+ <td class="fail">fail</td>
+ <% end %>
+</tr>
+<% end %>
+</table>
+<% end %>
diff --git a/report-generators/test/example.schedule b/report-generators/test/example.schedule
new file mode 100644
index 00000000..f617187a
--- /dev/null
+++ b/report-generators/test/example.schedule
@@ -0,0 +1,4 @@
+# This is a comment
+description number 1:$TEST_TOOL ls
+foo bar: $TEST_TOOL du -hs .
+ this comment is prefixed with whitespace: $TEST_TOOL date \ No newline at end of file
diff --git a/report-generators/test/strings/more_strings/test3.txt b/report-generators/test/strings/more_strings/test3.txt
new file mode 100644
index 00000000..3e9ffe06
--- /dev/null
+++ b/report-generators/test/strings/more_strings/test3.txt
@@ -0,0 +1 @@
+lorem
diff --git a/report-generators/test/strings/test1.txt b/report-generators/test/strings/test1.txt
new file mode 100644
index 00000000..af5626b4
--- /dev/null
+++ b/report-generators/test/strings/test1.txt
@@ -0,0 +1 @@
+Hello, world!
diff --git a/report-generators/test/strings/test2 b/report-generators/test/strings/test2
new file mode 100644
index 00000000..54d55bf0
--- /dev/null
+++ b/report-generators/test/strings/test2
@@ -0,0 +1,3 @@
+one
+two
+three \ No newline at end of file
diff --git a/report-generators/test/tc_log.rb b/report-generators/test/tc_log.rb
new file mode 100644
index 00000000..a7e9023c
--- /dev/null
+++ b/report-generators/test/tc_log.rb
@@ -0,0 +1,26 @@
+require 'test/unit'
+require 'stringio'
+require 'log'
+
+class TestLog < Test::Unit::TestCase
+ include Log
+
+ private
+ def remove_timestamps(l)
+ l.gsub(/\[[^\]]*\]/, '')
+ end
+
+ public
+ def test_log
+ StringIO.open do |out|
+ init(out)
+
+ info("msg1")
+ warning("msg2")
+ debug("msg3")
+
+ assert_equal("I, INFO -- : msg1\nW, WARN -- : msg2\nD, DEBUG -- : msg3\n",
+ remove_timestamps(out.string))
+ end
+ end
+end
diff --git a/report-generators/test/tc_schedule_file.rb b/report-generators/test/tc_schedule_file.rb
new file mode 100644
index 00000000..ec4bc3ea
--- /dev/null
+++ b/report-generators/test/tc_schedule_file.rb
@@ -0,0 +1,28 @@
+require 'test/unit'
+require 'pathname'
+require 'schedule_file'
+
+class TestScheduleFile < Test::Unit::TestCase
+ def test_reading
+ p = Pathname.new("report-generators/test/example.schedule")
+ p.open do |f|
+ s = Schedule.read(p.dirname, f)
+
+ assert_equal(3, s.schedules.size)
+ assert_equal(s.schedules[2].desc, "this comment is prefixed with whitespace")
+ assert_equal(s.schedules[0].command_line, "$TEST_TOOL ls")
+ end
+ end
+
+ def test_running
+ p = Pathname.new("report-generators/test/example.schedule")
+ p.open do |f|
+ s = Schedule.read(p.dirname, f)
+ s.run
+
+ s.schedules.each do |t|
+ assert(t.status.success?)
+ end
+ end
+ end
+end
diff --git a/report-generators/test/tc_string_store.rb b/report-generators/test/tc_string_store.rb
new file mode 100644
index 00000000..5ff89dd8
--- /dev/null
+++ b/report-generators/test/tc_string_store.rb
@@ -0,0 +1,19 @@
+require 'string-store'
+require 'test/unit'
+
+class TestStringStore < Test::Unit::TestCase
+ def setup
+ @ss = StringStore.new(['report-generators/test/strings',
+ 'report-generators/test/strings/more_strings'])
+ end
+
+ def test_lookup
+ assert_equal("Hello, world!\n", @ss.lookup(:test1))
+ assert_equal("one\ntwo\nthree", @ss.lookup(:test2))
+ assert_equal("lorem\n", @ss.lookup(:test3))
+
+ assert_raises(RuntimeError) do
+ @ss.lookup(:unlikely_name)
+ end
+ end
+end
diff --git a/report-generators/test/ts.rb b/report-generators/test/ts.rb
new file mode 100644
index 00000000..0a8cc910
--- /dev/null
+++ b/report-generators/test/ts.rb
@@ -0,0 +1,3 @@
+require 'tc_log'
+require 'tc_string_store'
+require 'tc_schedule_file'
diff --git a/report-generators/title_page.rb b/report-generators/title_page.rb
new file mode 100644
index 00000000..f6873eb1
--- /dev/null
+++ b/report-generators/title_page.rb
@@ -0,0 +1,32 @@
+# This generates the index for the reports, including generation
+# times.
+
+require 'log'
+require 'string-store'
+require 'reports'
+require 'erb'
+require 'report_templates'
+
+include Reports
+
+reports = ReportRegister.new
+
+def safe_mtime(r)
+ r.path.file? ? r.path.mtime.to_s : "not generated"
+end
+
+template_store = TemplateStringStore.new
+
+# FIXME: use generate_report() method
+erb = ERB.new(template_store.lookup("index.rhtml"))
+body = erb.result(binding)
+title = "Generation times"
+
+erb = ERB.new(template_store.lookup("boiler_plate.rhtml"))
+txt = erb.result(binding)
+
+Pathname.new("reports/index.html").open("w") do |f|
+ f.puts txt
+end
+
+
diff --git a/report-generators/unit_test.rb b/report-generators/unit_test.rb
new file mode 100644
index 00000000..ebc37855
--- /dev/null
+++ b/report-generators/unit_test.rb
@@ -0,0 +1,46 @@
+# Reads the schedule files given on the command line. Runs them and
+# generates the reports.
+
+require 'schedule_file'
+require 'pathname'
+require 'reports'
+require 'erb'
+require 'report_templates'
+
+include ReportTemplates
+
+schedules = ARGV.map do |f|
+ p = Pathname.new(f)
+ Schedule.read(p.dirname, p)
+end
+
+total_passed = 0
+total_failed = 0
+
+# We need to make sure the lvm shared libs are in the LD_LIBRARY_PATH
+ENV['LD_LIBRARY_PATH'] = `pwd`.strip + "/libdm:" + (ENV['LD_LIBRARY_PATH'] || '')
+
+schedules.each do |s|
+ s.run
+
+ s.schedules.each do |t|
+ if t.status.success?
+ total_passed += 1
+ else
+ total_failed += 1
+ end
+ end
+end
+
+def mangle(txt)
+ txt.gsub(/\s+/, '_')
+end
+
+generate_report(:unit_test, binding)
+
+# now we generate a detail report for each schedule
+schedules.each do |s|
+ s.schedules.each do |t|
+ generate_report(:unit_detail, binding, Pathname.new("reports/detail_#{mangle(t.desc)}.html"))
+ end
+end