added even listeners, and replaced output and reporting system with listeners
This commit is contained in:
parent
fd5299f8a9
commit
2f2919c156
|
@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["Nick Gauthier"]
|
||||
s.date = %q{2010-02-18}
|
||||
s.date = %q{2010-03-25}
|
||||
s.description = %q{Spread your tests over multiple machines to test your code faster.}
|
||||
s.email = %q{nick@smartlogicsolutions.com}
|
||||
s.extra_rdoc_files = [
|
||||
|
@ -30,6 +30,9 @@ Gem::Specification.new do |s|
|
|||
"hydra_gray.png",
|
||||
"lib/hydra.rb",
|
||||
"lib/hydra/hash.rb",
|
||||
"lib/hydra/listener/abstract.rb",
|
||||
"lib/hydra/listener/minimal_output.rb",
|
||||
"lib/hydra/listener/report_generator.rb",
|
||||
"lib/hydra/master.rb",
|
||||
"lib/hydra/message.rb",
|
||||
"lib/hydra/message/master_messages.rb",
|
||||
|
|
|
@ -7,4 +7,8 @@ require 'hydra/safe_fork'
|
|||
require 'hydra/runner'
|
||||
require 'hydra/worker'
|
||||
require 'hydra/master'
|
||||
require 'hydra/listener/abstract'
|
||||
require 'hydra/listener/minimal_output'
|
||||
require 'hydra/listener/report_generator'
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
module Hydra #:nodoc:
|
||||
module Listener #:nodoc:
|
||||
# Abstract listener that implements all the events
|
||||
# but does nothing.
|
||||
class Abstract
|
||||
# Create a new listener.
|
||||
#
|
||||
# Output: The IO object for outputting any information.
|
||||
# Defaults to STDOUT, but you could pass a file in, or STDERR
|
||||
def initialize(output = $stdout)
|
||||
@output = output
|
||||
end
|
||||
# Fired when testing has started
|
||||
def testing_begin(files)
|
||||
end
|
||||
|
||||
# Fired when testing finishes
|
||||
def testing_end
|
||||
end
|
||||
|
||||
# Fired when a file is started
|
||||
def file_begin(file)
|
||||
end
|
||||
|
||||
# Fired when a file is finished
|
||||
def file_end(file, output)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
module Hydra #:nodoc:
|
||||
module Listener #:nodoc:
|
||||
# Minimal output listener. Outputs all the files at the start
|
||||
# of testing and outputs a ./F/E per file. As well as
|
||||
# full error output, if any.
|
||||
class MinimalOutput < Hydra::Listener::Abstract
|
||||
def testing_begin(files)
|
||||
@output.write "Hydra Testing:\n#{files.inspect}\n"
|
||||
end
|
||||
|
||||
def testing_end
|
||||
@output.write "\nHydra Completed\n"
|
||||
end
|
||||
|
||||
def file_end(file, output)
|
||||
@output.write output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
module Hydra #:nodoc:
|
||||
module Listener #:nodoc:
|
||||
# Output a textual report at the end of testing
|
||||
class ReportGenerator < Hydra::Listener::Abstract
|
||||
def testing_begin(files)
|
||||
@report = { }
|
||||
end
|
||||
|
||||
def file_begin(file)
|
||||
@report[file] ||= { }
|
||||
@report[file]['start'] = Time.now.to_f
|
||||
end
|
||||
|
||||
def file_end(file, output)
|
||||
@report[file]['end'] = Time.now.to_f
|
||||
@report[file]['duration'] = @report[file]['end'] - @report[file]['start']
|
||||
end
|
||||
|
||||
def testing_end
|
||||
YAML.dump(@report, @output)
|
||||
@output.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,6 +19,13 @@ module Hydra #:nodoc:
|
|||
# * :workers
|
||||
# * An array of hashes. Each hash should be the configuration options
|
||||
# for a worker.
|
||||
# * :listeners
|
||||
# * An array of Hydra::Listener objects. See Hydra::Listener::MinimalOutput for an
|
||||
# example listener
|
||||
# * :verbose
|
||||
# * Set to true to see lots of Hydra output (for debugging)
|
||||
# * :autosort
|
||||
# * Set to false to disable automatic sorting by historical run-time per file
|
||||
def initialize(opts = { })
|
||||
opts.stringify_keys!
|
||||
config_file = opts.delete('config') { nil }
|
||||
|
@ -30,13 +37,16 @@ module Hydra #:nodoc:
|
|||
@incomplete_files = @files.dup
|
||||
@workers = []
|
||||
@listeners = []
|
||||
@event_listeners = Array(opts.fetch('listeners') { nil } )
|
||||
@verbose = opts.fetch('verbose') { false }
|
||||
@report = opts.fetch('report') { false }
|
||||
@autosort = opts.fetch('autosort') { true }
|
||||
sort_files_from_report if @autosort
|
||||
init_report_file
|
||||
@sync = opts.fetch('sync') { nil }
|
||||
|
||||
if @autosort
|
||||
sort_files_from_report
|
||||
@event_listeners << Hydra::Listener::ReportGenerator.new(File.new(heuristic_file, 'w'))
|
||||
end
|
||||
|
||||
# default is one worker that is configured to use a pipe with one runner
|
||||
worker_cfg = opts.fetch('workers') { [ { 'type' => 'local', 'runners' => 1} ] }
|
||||
|
||||
|
@ -45,6 +55,8 @@ module Hydra #:nodoc:
|
|||
trace " Workers: (#{worker_cfg.inspect})"
|
||||
trace " Verbose: (#{@verbose.inspect})"
|
||||
|
||||
@event_listeners.each{|l| l.testing_begin(@files) }
|
||||
|
||||
boot_workers worker_cfg
|
||||
process_messages
|
||||
end
|
||||
|
@ -56,7 +68,7 @@ module Hydra #:nodoc:
|
|||
f = @files.shift
|
||||
if f
|
||||
trace "Sending #{f.inspect}"
|
||||
report_start_time(f)
|
||||
@event_listeners.each{|l| l.file_begin(f) }
|
||||
worker[:io].write(RunFile.new(:file => f))
|
||||
else
|
||||
trace "No more files to send"
|
||||
|
@ -65,11 +77,9 @@ module Hydra #:nodoc:
|
|||
|
||||
# Process the results coming back from the worker.
|
||||
def process_results(worker, message)
|
||||
$stdout.write message.output
|
||||
# only delete one
|
||||
@incomplete_files.delete_at(@incomplete_files.index(message.file))
|
||||
trace "#{@incomplete_files.size} Files Remaining"
|
||||
report_finish_time(message.file)
|
||||
@event_listeners.each{|l| l.file_end(message.file, message.output) }
|
||||
if @incomplete_files.empty?
|
||||
shutdown_all_workers
|
||||
else
|
||||
|
@ -185,60 +195,25 @@ module Hydra #:nodoc:
|
|||
end
|
||||
|
||||
@listeners.each{|l| l.join}
|
||||
|
||||
generate_report
|
||||
end
|
||||
|
||||
def init_report_file
|
||||
FileUtils.rm_f(report_file)
|
||||
FileUtils.rm_f(report_results_file)
|
||||
end
|
||||
|
||||
def report_start_time(file)
|
||||
File.open(report_file, 'a'){|f| f.write "#{file}|start|#{Time.now.to_f}\n" }
|
||||
end
|
||||
|
||||
def report_finish_time(file)
|
||||
File.open(report_file, 'a'){|f| f.write "#{file}|finish|#{Time.now.to_f}\n" }
|
||||
end
|
||||
|
||||
def generate_report
|
||||
report = {}
|
||||
lines = nil
|
||||
File.open(report_file, 'r'){|f| lines = f.read.split("\n")}
|
||||
lines.each{|l| l = l.split('|'); report[l[0]] ||= {}; report[l[0]][l[1]] = l[2]}
|
||||
report.each{|file, times| report[file]['duration'] = times['finish'].to_f - times['start'].to_f}
|
||||
report = report.sort{|a, b| b[1]['duration'] <=> a[1]['duration']}
|
||||
output = []
|
||||
report.each{|file, times| output << "%.2f\t#{file}" % times['duration']}
|
||||
@report_text = output.join("\n")
|
||||
File.open(report_results_file, 'w'){|f| f.write @report_text}
|
||||
return report_text
|
||||
end
|
||||
|
||||
def reported_files
|
||||
return [] unless File.exists?(report_results_file)
|
||||
rep = []
|
||||
File.open(report_results_file, 'r') do |f|
|
||||
lines = f.read.split("\n")
|
||||
lines.each{|l| rep << l.split(" ")[1] }
|
||||
end
|
||||
return rep
|
||||
@event_listeners.each{|l| l.testing_end}
|
||||
end
|
||||
|
||||
def sort_files_from_report
|
||||
sorted_files = reported_files
|
||||
reported_files.each do |f|
|
||||
@files.push(@files.delete_at(@files.index(f))) if @files.index(f)
|
||||
if File.exists? heuristic_file
|
||||
report = YAML.load_file(heuristic_file)
|
||||
return unless report
|
||||
sorted_files = report.sort{ |a,b|
|
||||
b[1]['duration'] <=> a[1]['duration']
|
||||
}.collect{|tuple| tuple[0]}
|
||||
|
||||
sorted_files.each do |f|
|
||||
@files.push(@files.delete_at(@files.index(f))) if @files.index(f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def report_file
|
||||
@report_file ||= File.join(Dir.tmpdir, 'hydra_report.txt')
|
||||
end
|
||||
|
||||
def report_results_file
|
||||
@report_results_file ||= File.join(Dir.tmpdir, 'hydra_report_results.txt')
|
||||
def heuristic_file
|
||||
@heuristic_file ||= File.join(Dir.tmpdir, 'hydra_heuristics.yml')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,7 +56,7 @@ module Hydra #:nodoc:
|
|||
# t.add_files 'test/functional/**/*_test.rb'
|
||||
# t.add_files 'test/integration/**/*_test.rb'
|
||||
# t.verbose = false # optionally set to true for lots of debug messages
|
||||
# t.report = true # optionally set to true for a final report of test times
|
||||
# t.autosort = false # disable automatic sorting based on runtime of tests
|
||||
# end
|
||||
class TestTask < Hydra::Task
|
||||
|
||||
|
@ -65,8 +65,8 @@ module Hydra #:nodoc:
|
|||
@name = name
|
||||
@files = []
|
||||
@verbose = false
|
||||
@report = false
|
||||
@autosort = true
|
||||
@listeners = [Hydra::Listener::MinimalOutput.new]
|
||||
|
||||
yield self if block_given?
|
||||
|
||||
|
@ -74,9 +74,9 @@ module Hydra #:nodoc:
|
|||
|
||||
@opts = {
|
||||
:verbose => @verbose,
|
||||
:report => @report,
|
||||
:autosort => @autosort,
|
||||
:files => @files
|
||||
:files => @files,
|
||||
:listeners => @listeners
|
||||
}
|
||||
if @config
|
||||
@opts.merge!(:config => @config)
|
||||
|
@ -92,10 +92,7 @@ module Hydra #:nodoc:
|
|||
def define
|
||||
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
||||
task @name do
|
||||
$stdout.write "Hydra Testing #{files.inspect}\n"
|
||||
h = Hydra::Master.new(@opts)
|
||||
$stdout.write "\n"+h.report_text if @report
|
||||
$stdout.write "\nHydra Completed\n"
|
||||
Hydra::Master.new(@opts)
|
||||
exit(0) #bypass test on_exit output
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,15 +21,13 @@ class MasterTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
should "generate a report" do
|
||||
Hydra::Master.new(
|
||||
:files => [test_file],
|
||||
:report => true
|
||||
)
|
||||
Hydra::Master.new(:files => [test_file])
|
||||
assert File.exists?(target_file)
|
||||
assert_equal "HYDRA", File.read(target_file)
|
||||
report_file = File.join(Dir.tmpdir, 'hydra_report.txt')
|
||||
report_file = File.join(Dir.tmpdir, 'hydra_heuristics.yml')
|
||||
assert File.exists?(report_file)
|
||||
assert_equal 2, File.read(report_file).split("\n").size
|
||||
assert report = YAML.load_file(report_file)
|
||||
assert_not_nil report[test_file]
|
||||
end
|
||||
|
||||
should "run a test 6 times on 1 worker with 2 runners" do
|
||||
|
@ -119,19 +117,13 @@ class MasterTest < Test::Unit::TestCase
|
|||
# ensure b is on remote
|
||||
assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
|
||||
|
||||
# fake as if the test got run, so only the sync code is really being tested
|
||||
fake_result = Hydra::Messages::Worker::Results.new(
|
||||
:file => 'test_a.rb', :output => '.'
|
||||
).serialize.inspect
|
||||
|
||||
Hydra::Master.new(
|
||||
:files => ['test_a.rb'],
|
||||
:workers => [{
|
||||
:type => :ssh,
|
||||
:connect => 'localhost',
|
||||
:directory => remote,
|
||||
:runners => 1,
|
||||
:command => "ruby -e 'puts #{fake_result}' && exit"
|
||||
:runners => 1
|
||||
}],
|
||||
:sync => {
|
||||
:directory => local,
|
||||
|
|
Loading…
Reference in New Issue