From 6544e9eec3237b7a7de74a2e6c4b179cd5f79fba Mon Sep 17 00:00:00 2001 From: Nick Gauthier Date: Wed, 10 Feb 2010 11:38:06 -0500 Subject: [PATCH] Remote and Global tasks --- hydra.gemspec | 2 +- lib/hydra/tasks.rb | 136 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 120 insertions(+), 18 deletions(-) diff --git a/hydra.gemspec b/hydra.gemspec index 416c4cc..fd64184 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-02-09} + s.date = %q{2010-02-10} s.description = %q{Spread your tests over multiple machines to test your code faster.} s.email = %q{nick@smartlogicsolutions.com} s.extra_rdoc_files = [ diff --git a/lib/hydra/tasks.rb b/lib/hydra/tasks.rb index b6e0d2e..05b538e 100644 --- a/lib/hydra/tasks.rb +++ b/lib/hydra/tasks.rb @@ -1,8 +1,7 @@ +require 'open3' module Hydra #:nodoc: - # Define a test task that uses hydra to test the files. - # - # TODO: examples - class TestTask + # Hydra Task Common attributes and methods + class Task # Name of the task. Default 'hydra' attr_accessor :name @@ -19,6 +18,35 @@ module Hydra #:nodoc: # Path to the hydra config file. # If not set, it will check 'hydra.yml' and 'config/hydra.yml' attr_accessor :config + # + # Search for the hydra config file + def find_config_file + @config ||= 'hydra.yml' + return @config if File.exists?(@config) + @config = File.join('config', 'hydra.yml') + return @config if File.exists?(@config) + @config = nil + end + + # Add files to test by passing in a string to be run through Dir.glob. + # For example: + # + # t.add_files 'test/units/*.rb' + def add_files(pattern) + @files += Dir.glob(pattern) + end + + end + + # Define a test task that uses hydra to test the files. + # + # Hydra::TestTask.new('hydra') do |t| + # t.add_files 'test/unit/**/*_test.rb' + # 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 + # end + class TestTask < Hydra::Task # Create a new HydraTestTask def initialize(name = :hydra) @@ -45,6 +73,7 @@ module Hydra #:nodoc: define end + private # Create the rake task defined by this HydraTestTask def define desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}") @@ -55,22 +84,95 @@ module Hydra #:nodoc: exit(0) #bypass test on_exit output end end + end - # Add files to test by passing in a string to be run through Dir.glob. - # For example: - # - # t.add_files 'test/units/*.rb' - def add_files(pattern) - @files += Dir.glob(pattern) + # Setup a task that will be run across all remote workers + # Hydra::RemoteTask.new('db:reset') + # + # Then you can run: + # rake hydra:remote:db:reset + class RemoteTask < Hydra::Task + include Open3 + # Create a new hydra remote task with the given name. + # The task will be named hydra:remote: + def initialize(name) + @name = name + yield self if block_given? + @config = find_config_file + + unless @config + $stderr.write "No config file. Can't run a remote task without remote workers\n" + return + end + + define end - # Search for the hydra config file - def find_config_file - @config ||= 'hydra.yml' - return @config if File.exists?(@config) - @config = File.join('config', 'hydra.yml') - return @config if File.exists?(@config) - @config = nil + private + def define + desc "Run #{@name} remotely on all workers" + task "hydra:remote:#{@name}" do + config = YAML.load_file(@config) + workers = config.fetch('workers') { [] } + workers = workers.select{|w| w['type'] == 'ssh'} + raise "No remote workers" if workers.empty? + workers.each do |worker| + $stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n" + ssh_opts = worker.fetch('ssh_opts') { '' } + writer, reader, error = popen3("ssh -tt #{ssh_opts} #{worker['connect']} ") + writer.write("cd #{worker['directory']}\n") + writer.write "echo BEGIN HYDRA\n" + writer.write("RAILS_ENV=test rake #{@name}\n") + writer.write "echo END HYDRA\n" + writer.write("exit\n") + writer.close + ignoring = true + while line = reader.gets + line.chomp! + if line =~ /echo END HYDRA$/ + ignoring = true + end + $stdout.write "#{line}\n" unless ignoring + if line == 'BEGIN HYDRA' + ignoring = false + end + end + $stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n" + end + end + end + end + + # A Hydra global task is a task that is run both locally and remotely. + # + # For example: + # + # Hydra::GlobalTask.new('db:reset') + # + # Allows you to run: + # + # rake hydra:db:reset + # + # Then, db:reset will be run locally and on all remote workers. This + # makes it easy to setup your workers and run tasks all in a row. + # + # For example: + # + # rake hydra:db:reset hydra:factories hydra:tests + # + # Assuming you setup hydra:db:reset and hydra:db:factories as global + # tasks and hydra:tests as a Hydra::TestTask for all your tests + class GlobalTask < Hydra::Task + def initialize(name) + @name = name + define + end + + private + def define + Hydra::RemoteTask.new(@name) + desc "Run #{@name.to_s} Locally and Remotely across all Workers" + task "hydra:#{@name.to_s}" => [@name.to_s, "hydra:remote:#{@name.to_s}"] end end end