Skip to content

Instantly share code, notes, and snippets.

@joerichsen
Created April 30, 2010 21:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joerichsen/385761 to your computer and use it in GitHub Desktop.
Save joerichsen/385761 to your computer and use it in GitHub Desktop.
require 'test/unit'
require 'test/unit/testresult'
Test::Unit.run = true
module Hydra #:nodoc:
# Hydra class responsible for running test files.
#
# The Runner is never run directly by a user. Runners are created by a
# Worker to run test files.
#
# The general convention is to have one Runner for each logical processor
# of a machine.
class Runner
include Hydra::Messages::Runner
traceable('RUNNER')
# Boot up a runner. It takes an IO object (generally a pipe from its
# parent) to send it messages on which files to execute.
def initialize(opts = {})
@io = opts.fetch(:io) { raise "No IO Object" }
@verbose = opts.fetch(:verbose) { false }
$stdout.sync = true
trace 'Creating test database'
ENV['TEST_ENV_NUMBER'] = Process.pid.to_s
Rake::Task['db:reset'].invoke
trace 'Booted. Sending Request for file'
@io.write RequestFile.new
begin
process_messages
rescue => ex
trace ex.to_s
raise ex
end
end
# Run a test file and report the results
def run_file(file)
trace "Running file: #{file}"
output = ""
if file =~ /_spec.rb$/
output = run_rspec_file(file)
elsif file =~ /.feature$/
output = run_cucumber_file(file)
else
output = run_test_unit_file(file)
end
output = "." if output == ""
@io.write Results.new(:output => output, :file => file)
return output
end
# Stop running
def stop
trace 'Dropping test database'
ENV['TEST_ENV_NUMBER'] = Process.pid.to_s
Rake::Task['db:drop'].invoke
@running = false
end
private
# The runner will continually read messages and handle them.
def process_messages
trace "Processing Messages"
@running = true
while @running
begin
message = @io.gets
if message and !message.class.to_s.index("Worker").nil?
trace "Received message from worker"
trace "\t#{message.inspect}"
message.handle(self)
else
@io.write Ping.new
end
rescue IOError => ex
trace "Runner lost Worker"
@running = false
end
end
end
# Run all the Test::Unit Suites in a ruby file
def run_test_unit_file(file)
begin
require file
rescue LoadError => ex
trace "#{file} does not exist [#{ex.to_s}]"
return ex.to_s
end
output = []
@result = Test::Unit::TestResult.new
@result.add_listener(Test::Unit::TestResult::FAULT) do |value|
output << value
end
klasses = Runner.find_classes_in_file(file)
begin
klasses.each{|klass| klass.suite.run(@result){|status, name| ;}}
rescue => ex
output << ex.to_s
end
return output.join("\n")
end
# run all the Specs in an RSpec file (NOT IMPLEMENTED)
def run_rspec_file(file)
# pull in rspec
begin
require 'spec'
require 'hydra/spec/hydra_formatter'
# Ensure we override rspec's at_exit
require 'hydra/spec/autorun_override'
rescue LoadError => ex
return ex.to_s
end
hydra_output = StringIO.new
Spec::Runner.options.instance_variable_set(:@formatters, [
Spec::Runner::Formatter::HydraFormatter.new(
Spec::Runner.options.formatter_options,
hydra_output
)
])
Spec::Runner.options.instance_variable_set(
:@example_groups, []
)
Spec::Runner.options.instance_variable_set(
:@files, [file]
)
Spec::Runner.options.instance_variable_set(
:@files_loaded, false
)
Spec::Runner.options.run_examples
hydra_output.rewind
output = hydra_output.read.chomp
output = "" if output.gsub("\n","") =~ /^\.*$/
return output
end
# run all the scenarios in a cucumber feature file
def run_cucumber_file(file)
files = [file]
dev_null = StringIO.new
hydra_response = StringIO.new
unless @step_mother
require 'cucumber'
require 'hydra/cucumber/formatter'
@step_mother = Cucumber::StepMother.new
@cuke_configuration = Cucumber::Cli::Configuration.new(dev_null, dev_null)
@cuke_configuration.parse!(['features']+files)
@step_mother.options = @cuke_configuration.options
@step_mother.log = @cuke_configuration.log
@step_mother.load_code_files(@cuke_configuration.support_to_load)
@step_mother.after_configuration(@cuke_configuration)
@step_mother.load_code_files(@cuke_configuration.step_defs_to_load)
end
cuke_formatter = Cucumber::Formatter::Hydra.new(
@step_mother, hydra_response, @cuke_configuration.options
)
cuke_runner ||= Cucumber::Ast::TreeWalker.new(
@step_mother, [cuke_formatter], @cuke_configuration.options, dev_null
)
@step_mother.visitor = cuke_runner
features = @step_mother.load_plain_text_features(files)
tag_excess = tag_excess(features, @cuke_configuration.options[:tag_expression].limits)
@cuke_configuration.options[:tag_excess] = tag_excess
cuke_runner.visit_features(features)
hydra_response.rewind
return hydra_response.read
end
# find all the test unit classes in a given file, so we can run their suites
def self.find_classes_in_file(f)
code = ""
File.open(f) {|buffer| code = buffer.read}
matches = code.scan(/class\s+([\S]+)/)
klasses = matches.collect do |c|
begin
if c.first.respond_to? :constantize
c.first.constantize
else
eval(c.first)
end
rescue NameError
# means we could not load [c.first], but thats ok, its just not
# one of the classes we want to test
nil
rescue SyntaxError
# see above
nil
end
end
return klasses.select{|k| k.respond_to? 'suite'}
end
# Yanked a method from Cucumber
def tag_excess(features, limits)
limits.map do |tag_name, tag_limit|
tag_locations = features.tag_locations(tag_name)
if tag_limit && (tag_locations.length > tag_limit)
[tag_name, tag_limit, tag_locations]
else
nil
end
end.compact
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment