diff --git a/README.rdoc b/README.rdoc index fe6d733..d1249e8 100644 --- a/README.rdoc +++ b/README.rdoc @@ -20,9 +20,12 @@ In your rakefile: require 'hydra/tasks' Hydra::TestTask.new('hydra') do |t| + # test unit t.add_files 'test/unit/**/*_test.rb' t.add_files 'test/functional/**/*_test.rb' t.add_files 'test/integration/**/*_test.rb' + # cucumber + t.add_files 'features/**/*.feature' end Run: @@ -32,6 +35,16 @@ Run: Hydra defaults to Single Core mode, so you may want to configure it to use two (or more) of your cores if you have a multi-processing machine. +== Supported frameworks + +Right now hydra only supports a few frameworks: + +* Test::Unit +* Cucumber + +We're working on adding more frameworks, and if you'd like to help, please +send me a message and I'll show you where to code! + == Running Remote Tasks You can run tasks across all of your remote workers easily with Hydra. In your rake file, add: diff --git a/hydra.gemspec b/hydra.gemspec index 9ee4dca..9cf621f 100644 --- a/hydra.gemspec +++ b/hydra.gemspec @@ -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-03-25} + s.date = %q{2010-03-30} 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,7 @@ Gem::Specification.new do |s| "hydra.gemspec", "hydra_gray.png", "lib/hydra.rb", + "lib/hydra/cucumber/formatter.rb", "lib/hydra/hash.rb", "lib/hydra/listener/abstract.rb", "lib/hydra/listener/minimal_output.rb", @@ -52,6 +53,8 @@ Gem::Specification.new do |s| "lib/hydra/worker.rb", "test/fixtures/assert_true.rb", "test/fixtures/config.yml", + "test/fixtures/features/step_definitions.rb", + "test/fixtures/features/write_file.feature", "test/fixtures/hello_world.rb", "test/fixtures/slow.rb", "test/fixtures/sync_test.rb", @@ -78,6 +81,7 @@ Gem::Specification.new do |s| "test/fixtures/sync_test.rb", "test/fixtures/assert_true.rb", "test/fixtures/hello_world.rb", + "test/fixtures/features/step_definitions.rb", "test/master_test.rb", "test/worker_test.rb", "test/runner_test.rb", diff --git a/lib/hydra/cucumber/formatter.rb b/lib/hydra/cucumber/formatter.rb new file mode 100644 index 0000000..274be93 --- /dev/null +++ b/lib/hydra/cucumber/formatter.rb @@ -0,0 +1,31 @@ +require 'cucumber/formatter/console' +require 'cucumber/formatter/io' + +module Cucumber #:nodoc: + module Formatter #:nodoc: + # Hydra formatter for cucumber. + # Stifles all output except error messages + # Based on the + class Hydra < Cucumber::Formatter::Progress + # Removed the extra newlines here + def after_features(features) + print_summary(features) + end + + private + + # Removed the file statistics + def print_summary(features) + print_steps(:pending) + print_steps(:failed) + print_snippets(@options) + print_passing_wip(@options) + print_tag_limit_warnings(features) + end + + # Removed all progress output + def progress(status) + end + end + end +end diff --git a/lib/hydra/runner.rb b/lib/hydra/runner.rb index aa293cd..200bdc1 100644 --- a/lib/hydra/runner.rb +++ b/lib/hydra/runner.rb @@ -33,29 +33,17 @@ module Hydra #:nodoc: # Run a test file and report the results def run_file(file) trace "Running file: #{file}" - begin - require file - rescue LoadError => ex - trace "#{file} does not exist [#{ex.to_s}]" - @io.write Results.new(:output => ex.to_s, :file => file) - return - end - output = [] - @result = Test::Unit::TestResult.new - @result.add_listener(Test::Unit::TestResult::FAULT) do |value| - output << value + + output = "" + if file =~ /.rb$/ + output = run_ruby_file(file) + elsif file =~ /.feature$/ + output = run_cucumber_file(file) 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 + output = "." if output == "" - output << '.' if output.empty? - - @io.write Results.new(:output => output.join("\n"), :file => file) + @io.write Results.new(:output => output, :file => file) end # Stop running @@ -86,6 +74,94 @@ module Hydra #:nodoc: end end + # Run a ruby file (ending in .rb) + def run_ruby_file(file) + run_test_unit_file(file) + run_rspec_file(file) + 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) + #TODO + # Given the file + # return "" if all the tests passed + # or return the error messages for the entire file + return "" + end + + # run all the scenarios in a cucumber feature file + def run_cucumber_file(file) + require 'cucumber' + require 'cucumber/formatter/progress' + require 'hydra/cucumber/formatter' + 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 + + files = [file] + dev_null = StringIO.new + + options = Cucumber::Cli::Options.new + configuration = Cucumber::Cli::Configuration.new(dev_null, dev_null) + configuration.parse!([]+files) + step_mother = Cucumber::StepMother.new + + step_mother.options = configuration.options + step_mother.log = configuration.log + step_mother.load_code_files(configuration.support_to_load) + step_mother.after_configuration(configuration) + features = step_mother.load_plain_text_features(files) + step_mother.load_code_files(configuration.step_defs_to_load) + + tag_excess = tag_excess(features, configuration.options[:tag_expression].limits) + configuration.options[:tag_excess] = tag_excess + + hydra_response = StringIO.new + formatter = Cucumber::Formatter::Hydra.new( + step_mother, hydra_response, configuration.options + ) + + runner = Cucumber::Ast::TreeWalker.new( + step_mother, [formatter], configuration.options, dev_null + ) + step_mother.visitor = runner + 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} diff --git a/test/fixtures/features/step_definitions.rb b/test/fixtures/features/step_definitions.rb new file mode 100644 index 0000000..d6cbfc3 --- /dev/null +++ b/test/fixtures/features/step_definitions.rb @@ -0,0 +1,17 @@ +Given /^a target file$/ do + @target_file = File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt')) +end + +When /^I write "([^\"]*)" to the file$/ do |text| + f = File.new(@target_file, 'w') + f.write text + f.flush + f.close +end + +Then /^"([^\"]*)" should be written in the file$/ do |text| + f = File.new(@target_file, 'r') + raise 'Did not write to file' unless text == f.read + f.close +end + diff --git a/test/fixtures/features/write_file.feature b/test/fixtures/features/write_file.feature new file mode 100644 index 0000000..92e87da --- /dev/null +++ b/test/fixtures/features/write_file.feature @@ -0,0 +1,7 @@ +Feature: Write a file + + Scenario: Write to hydra_test.txt + Given a target file + When I write "HYDRA" to the file + Then "HYDRA" should be written in the file + diff --git a/test/runner_test.rb b/test/runner_test.rb index fab6686..df378ea 100644 --- a/test/runner_test.rb +++ b/test/runner_test.rb @@ -18,7 +18,7 @@ class RunnerTest < Test::Unit::TestCase # coverage output pipe = Hydra::Pipe.new parent = Process.fork do - request_a_file_and_verify_completion(pipe) + request_a_file_and_verify_completion(pipe, test_file) end run_the_runner(pipe) Process.wait(parent) @@ -31,10 +31,19 @@ class RunnerTest < Test::Unit::TestCase child = Process.fork do run_the_runner(pipe) end - request_a_file_and_verify_completion(pipe) + request_a_file_and_verify_completion(pipe, test_file) Process.wait(child) end + should "run a cucumber test" do + pipe = Hydra::Pipe.new + parent = Process.fork do + request_a_file_and_verify_completion(pipe, cucumber_feature_file) + end + run_the_runner(pipe) + Process.wait(parent) + end + should "be able to run a runner over ssh" do ssh = Hydra::SSH.new( 'localhost', @@ -62,12 +71,12 @@ class RunnerTest < Test::Unit::TestCase end module RunnerTestHelper - def request_a_file_and_verify_completion(pipe) + def request_a_file_and_verify_completion(pipe, file) pipe.identify_as_parent # make sure it asks for a file, then give it one assert pipe.gets.is_a?(Hydra::Messages::Runner::RequestFile) - pipe.write(Hydra::Messages::Worker::RunFile.new(:file => test_file)) + pipe.write(Hydra::Messages::Worker::RunFile.new(:file => file)) # grab its response. This makes us wait for it to finish response = pipe.gets diff --git a/test/test_helper.rb b/test/test_helper.rb index 6f7260c..6d4e585 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -18,6 +18,10 @@ class Test::Unit::TestCase def test_file File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file.rb')) end + + def cucumber_feature_file + File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_file.feature')) + end end module Hydra #:nodoc: