causes-hydra 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +56 -0
- data/TODO +18 -0
- data/VERSION +1 -0
- data/bin/warmsnake.rb +76 -0
- data/caliper.yml +6 -0
- data/hydra-icon-64x64.png +0 -0
- data/hydra.gemspec +130 -0
- data/hydra_gray.png +0 -0
- data/lib/hydra.rb +16 -0
- data/lib/hydra/cucumber/formatter.rb +29 -0
- data/lib/hydra/hash.rb +16 -0
- data/lib/hydra/js/lint.js +5150 -0
- data/lib/hydra/listener/abstract.rb +39 -0
- data/lib/hydra/listener/minimal_output.rb +24 -0
- data/lib/hydra/listener/notifier.rb +17 -0
- data/lib/hydra/listener/progress_bar.rb +48 -0
- data/lib/hydra/listener/report_generator.rb +30 -0
- data/lib/hydra/master.rb +249 -0
- data/lib/hydra/message.rb +47 -0
- data/lib/hydra/message/master_messages.rb +19 -0
- data/lib/hydra/message/runner_messages.rb +52 -0
- data/lib/hydra/message/worker_messages.rb +52 -0
- data/lib/hydra/messaging_io.rb +46 -0
- data/lib/hydra/pipe.rb +61 -0
- data/lib/hydra/runner.rb +305 -0
- data/lib/hydra/safe_fork.rb +31 -0
- data/lib/hydra/spec/autorun_override.rb +3 -0
- data/lib/hydra/spec/hydra_formatter.rb +26 -0
- data/lib/hydra/ssh.rb +41 -0
- data/lib/hydra/stdio.rb +16 -0
- data/lib/hydra/sync.rb +99 -0
- data/lib/hydra/tasks.rb +342 -0
- data/lib/hydra/trace.rb +24 -0
- data/lib/hydra/worker.rb +150 -0
- data/test/fixtures/assert_true.rb +7 -0
- data/test/fixtures/config.yml +4 -0
- data/test/fixtures/features/step_definitions.rb +21 -0
- data/test/fixtures/features/write_alternate_file.feature +7 -0
- data/test/fixtures/features/write_file.feature +7 -0
- data/test/fixtures/hello_world.rb +3 -0
- data/test/fixtures/js_file.js +4 -0
- data/test/fixtures/json_data.json +4 -0
- data/test/fixtures/slow.rb +9 -0
- data/test/fixtures/sync_test.rb +8 -0
- data/test/fixtures/write_file.rb +10 -0
- data/test/fixtures/write_file_alternate_spec.rb +10 -0
- data/test/fixtures/write_file_spec.rb +9 -0
- data/test/fixtures/write_file_with_pending_spec.rb +11 -0
- data/test/master_test.rb +152 -0
- data/test/message_test.rb +31 -0
- data/test/pipe_test.rb +38 -0
- data/test/runner_test.rb +153 -0
- data/test/ssh_test.rb +14 -0
- data/test/sync_test.rb +113 -0
- data/test/test_helper.rb +68 -0
- data/test/worker_test.rb +60 -0
- metadata +209 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
class SafeFork
|
2
|
+
def self.fork
|
3
|
+
begin
|
4
|
+
# remove our connection so it doesn't get cloned
|
5
|
+
connection = ActiveRecord::Base.remove_connection if defined?(ActiveRecord)
|
6
|
+
# fork a process
|
7
|
+
child = Process.fork do
|
8
|
+
begin
|
9
|
+
# create a new connection and perform the action
|
10
|
+
begin
|
11
|
+
ActiveRecord::Base.establish_connection((connection || {}).merge({:allow_concurrency => true})) if defined?(ActiveRecord)
|
12
|
+
rescue ActiveRecord::AdapterNotSpecified
|
13
|
+
# AR was defined but we didn't have a connection
|
14
|
+
end
|
15
|
+
yield
|
16
|
+
ensure
|
17
|
+
# make sure we remove the connection before we're done
|
18
|
+
ActiveRecord::Base.remove_connection if defined?(ActiveRecord)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
ensure
|
22
|
+
# make sure we re-establish the connection before returning to the main instance
|
23
|
+
begin
|
24
|
+
ActiveRecord::Base.establish_connection((connection || {}).merge({:allow_concurrency => true})) if defined?(ActiveRecord)
|
25
|
+
rescue ActiveRecord::AdapterNotSpecified
|
26
|
+
# AR was defined but we didn't have a connection
|
27
|
+
end
|
28
|
+
end
|
29
|
+
return child
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rspec/core/formatters/progress_formatter'
|
2
|
+
module RSpec
|
3
|
+
module Core
|
4
|
+
module Formatters
|
5
|
+
class HydraFormatter < ProgressFormatter
|
6
|
+
def example_passed(example)
|
7
|
+
end
|
8
|
+
|
9
|
+
def example_pending(example)
|
10
|
+
end
|
11
|
+
|
12
|
+
def example_failed(example)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Stifle the post-test summary
|
16
|
+
def dump_summary(duration, example, failure, pending)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Stifle pending specs
|
20
|
+
def dump_pending
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/lib/hydra/ssh.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'hydra/messaging_io'
|
3
|
+
module Hydra #:nodoc:
|
4
|
+
# Read and write with an ssh connection. For example:
|
5
|
+
# @ssh = Hydra::SSH.new(
|
6
|
+
# 'localhost', # connect to this machine
|
7
|
+
# '/home/user', # move to the home directory
|
8
|
+
# "ruby hydra/test/echo_the_dolphin.rb" # run the echo script
|
9
|
+
# )
|
10
|
+
# @message = Hydra::Messages::TestMessage.new("Hey there!")
|
11
|
+
# @ssh.write @message
|
12
|
+
# puts @ssh.gets.text
|
13
|
+
# => "Hey there!"
|
14
|
+
#
|
15
|
+
# Note that what ever process you run should respond with Hydra messages.
|
16
|
+
class SSH
|
17
|
+
include Open3
|
18
|
+
include Hydra::MessagingIO
|
19
|
+
|
20
|
+
# Initialize new SSH connection.
|
21
|
+
# The first parameter is passed directly to ssh for starting a connection.
|
22
|
+
# The second parameter is the directory to CD into once connected.
|
23
|
+
# The third parameter is the command to run
|
24
|
+
# So you can do:
|
25
|
+
# Hydra::SSH.new('-p 3022 user@server.com', '/home/user/Desktop', 'ls -l')
|
26
|
+
# To connect to server.com as user on port 3022, then CD to their desktop, then
|
27
|
+
# list all the files.
|
28
|
+
def initialize(connection_options, directory, command)
|
29
|
+
@writer, @reader, @error = popen3("ssh -tt #{connection_options}")
|
30
|
+
@writer.write("mkdir -p #{directory}\n")
|
31
|
+
@writer.write("cd #{directory}\n")
|
32
|
+
@writer.write(command+"\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Close the SSH connection
|
36
|
+
def close
|
37
|
+
@writer.write "exit\n"
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/hydra/stdio.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'hydra/messaging_io'
|
2
|
+
module Hydra #:nodoc:
|
3
|
+
# Read and write via stdout and stdin.
|
4
|
+
class Stdio
|
5
|
+
include Hydra::MessagingIO
|
6
|
+
|
7
|
+
# Initialize new Stdio
|
8
|
+
def initialize()
|
9
|
+
@reader = $stdin
|
10
|
+
@writer = $stdout
|
11
|
+
@reader.sync = true
|
12
|
+
@writer.sync = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
data/lib/hydra/sync.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module Hydra #:nodoc:
|
3
|
+
# Hydra class responsible for delegate work down to workers.
|
4
|
+
#
|
5
|
+
# The Sync is run once for each remote worker.
|
6
|
+
class Sync
|
7
|
+
traceable('SYNC')
|
8
|
+
self.class.traceable('SYNC MANY')
|
9
|
+
|
10
|
+
attr_reader :connect, :ssh_opts, :remote_dir
|
11
|
+
|
12
|
+
# Create a new Sync instance to rsync source from the local machine to a remote worker
|
13
|
+
#
|
14
|
+
# Arguments:
|
15
|
+
# * :worker_opts
|
16
|
+
# * A hash of the configuration options for a worker.
|
17
|
+
# * :sync
|
18
|
+
# * A hash of settings specifically for copying the source directory to be tested
|
19
|
+
# to the remote worked
|
20
|
+
# * :verbose
|
21
|
+
# * Set to true to see lots of Hydra output (for debugging)
|
22
|
+
def initialize(worker_opts, sync_opts, verbose = false)
|
23
|
+
worker_opts ||= {}
|
24
|
+
worker_opts.stringify_keys!
|
25
|
+
@verbose = verbose
|
26
|
+
@connect = worker_opts.fetch('connect') { raise "You must specify an SSH connection target" }
|
27
|
+
@ssh_opts = worker_opts.fetch('ssh_opts') { "" }
|
28
|
+
@remote_dir = worker_opts.fetch('directory') { raise "You must specify a remote directory" }
|
29
|
+
|
30
|
+
return unless sync_opts
|
31
|
+
sync_opts.stringify_keys!
|
32
|
+
@local_dir = sync_opts.fetch('directory') { raise "You must specify a synchronization directory" }
|
33
|
+
@exclude_paths = sync_opts.fetch('exclude') { [] }
|
34
|
+
|
35
|
+
trace "Initialized"
|
36
|
+
trace " Worker: (#{worker_opts.inspect})"
|
37
|
+
trace " Sync: (#{sync_opts.inspect})"
|
38
|
+
|
39
|
+
sync
|
40
|
+
end
|
41
|
+
|
42
|
+
def sync
|
43
|
+
#trace "Synchronizing with #{connect}\n\t#{sync_opts.inspect}"
|
44
|
+
exclude_opts = @exclude_paths.inject(''){|memo, path| memo += "--exclude=#{path} "}
|
45
|
+
|
46
|
+
rsync_command = [
|
47
|
+
'rsync',
|
48
|
+
'-avz',
|
49
|
+
'--delete',
|
50
|
+
exclude_opts,
|
51
|
+
File.expand_path(@local_dir)+'/',
|
52
|
+
"-e \"ssh #{@ssh_opts}\"",
|
53
|
+
"#{@connect}:#{@remote_dir}"
|
54
|
+
].join(" ")
|
55
|
+
trace rsync_command
|
56
|
+
trace `#{rsync_command}`
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.sync_many opts
|
60
|
+
opts.stringify_keys!
|
61
|
+
config_file = opts.delete('config') { nil }
|
62
|
+
if config_file
|
63
|
+
opts.merge!(YAML.load_file(config_file).stringify_keys!)
|
64
|
+
end
|
65
|
+
@verbose = opts.fetch('verbose') { false }
|
66
|
+
@sync = opts.fetch('sync') { {} }
|
67
|
+
|
68
|
+
workers_opts = opts.fetch('workers') { [] }
|
69
|
+
@remote_worker_opts = []
|
70
|
+
workers_opts.each do |worker_opts|
|
71
|
+
worker_opts.stringify_keys!
|
72
|
+
if worker_opts['type'].to_s == 'ssh'
|
73
|
+
@remote_worker_opts << worker_opts
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
trace "Initialized"
|
78
|
+
trace " Sync: (#{@sync.inspect})"
|
79
|
+
trace " Workers: (#{@remote_worker_opts.inspect})"
|
80
|
+
|
81
|
+
Thread.abort_on_exception = true
|
82
|
+
trace "Processing workers"
|
83
|
+
@listeners = []
|
84
|
+
@remote_worker_opts.each do |worker_opts|
|
85
|
+
@listeners << Thread.new do
|
86
|
+
begin
|
87
|
+
trace "Syncing #{worker_opts.inspect}"
|
88
|
+
Sync.new worker_opts, @sync, @verbose
|
89
|
+
rescue
|
90
|
+
trace "Syncing failed [#{worker_opts.inspect}]"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@listeners.each{|l| l.join}
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
data/lib/hydra/tasks.rb
ADDED
@@ -0,0 +1,342 @@
|
|
1
|
+
require 'open3'
|
2
|
+
module Hydra #:nodoc:
|
3
|
+
# Hydra Task Common attributes and methods
|
4
|
+
class Task
|
5
|
+
# Name of the task. Default 'hydra'
|
6
|
+
attr_accessor :name
|
7
|
+
|
8
|
+
# Files to test.
|
9
|
+
# You can add files manually via:
|
10
|
+
# t.files << [file1, file2, etc]
|
11
|
+
#
|
12
|
+
# Or you can use the add_files method
|
13
|
+
attr_accessor :files
|
14
|
+
|
15
|
+
# True if you want to see Hydra's message traces
|
16
|
+
attr_accessor :verbose
|
17
|
+
|
18
|
+
# Path to the hydra config file.
|
19
|
+
# If not set, it will check 'hydra.yml' and 'config/hydra.yml'
|
20
|
+
attr_accessor :config
|
21
|
+
|
22
|
+
# Automatically sort files using their historical runtimes.
|
23
|
+
# Defaults to true
|
24
|
+
# To disable:
|
25
|
+
# t.autosort = false
|
26
|
+
attr_accessor :autosort
|
27
|
+
|
28
|
+
# Event listeners. Defaults to the MinimalOutput listener.
|
29
|
+
# You can add additional listeners if you'd like. For example,
|
30
|
+
# on linux (with notify-send) you can add the notifier listener:
|
31
|
+
# t.listeners << Hydra::Listener::Notifier.new
|
32
|
+
attr_accessor :listeners
|
33
|
+
|
34
|
+
# Set to true if you want to run this task only on the local
|
35
|
+
# machine with one runner. A "Safe Mode" for some test
|
36
|
+
# files that may not play nice with others.
|
37
|
+
attr_accessor :serial
|
38
|
+
|
39
|
+
#
|
40
|
+
# Search for the hydra config file
|
41
|
+
def find_config_file
|
42
|
+
@config ||= 'hydra.yml'
|
43
|
+
return @config if File.exists?(@config)
|
44
|
+
@config = File.join('config', 'hydra.yml')
|
45
|
+
return @config if File.exists?(@config)
|
46
|
+
@config = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Add files to test by passing in a string to be run through Dir.glob.
|
50
|
+
# For example:
|
51
|
+
#
|
52
|
+
# t.add_files 'test/units/*.rb'
|
53
|
+
def add_files(pattern)
|
54
|
+
@files += Dir.glob(pattern)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# Define a test task that uses hydra to test the files.
|
60
|
+
#
|
61
|
+
# Hydra::TestTask.new('hydra') do |t|
|
62
|
+
# t.add_files 'test/unit/**/*_test.rb'
|
63
|
+
# t.add_files 'test/functional/**/*_test.rb'
|
64
|
+
# t.add_files 'test/integration/**/*_test.rb'
|
65
|
+
# t.verbose = false # optionally set to true for lots of debug messages
|
66
|
+
# t.autosort = false # disable automatic sorting based on runtime of tests
|
67
|
+
# end
|
68
|
+
class TestTask < Hydra::Task
|
69
|
+
|
70
|
+
# Create a new HydraTestTask
|
71
|
+
def initialize(name = :hydra)
|
72
|
+
@name = name
|
73
|
+
@files = []
|
74
|
+
@verbose = false
|
75
|
+
@autosort = true
|
76
|
+
@serial = false
|
77
|
+
@listeners = [Hydra::Listener::ProgressBar.new]
|
78
|
+
|
79
|
+
yield self if block_given?
|
80
|
+
|
81
|
+
# Ensure we override rspec's at_exit
|
82
|
+
require 'hydra/spec/autorun_override'
|
83
|
+
|
84
|
+
unless @serial
|
85
|
+
@config = find_config_file
|
86
|
+
end
|
87
|
+
|
88
|
+
@opts = {
|
89
|
+
:verbose => @verbose,
|
90
|
+
:autosort => @autosort,
|
91
|
+
:files => @files,
|
92
|
+
:listeners => @listeners
|
93
|
+
}
|
94
|
+
if @config
|
95
|
+
@opts.merge!(:config => @config)
|
96
|
+
else
|
97
|
+
@opts.merge!(:workers => [{:type => :local, :runners => 1}])
|
98
|
+
end
|
99
|
+
|
100
|
+
define
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
# Create the rake task defined by this HydraTestTask
|
105
|
+
def define
|
106
|
+
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
107
|
+
task @name do
|
108
|
+
if Object.const_defined?('RAILS_ENV') && RAILS_ENV == 'development'
|
109
|
+
$stderr.puts %{WARNING: RAILS_ENV is "development". Make sure to set it properly (ex: "RAILS_ENV=test rake hydra")}
|
110
|
+
end
|
111
|
+
|
112
|
+
Hydra::Master.new(@opts)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Define a test task that uses hydra to profile your test files
|
118
|
+
#
|
119
|
+
# Hydra::ProfileTask.new('hydra:prof') do |t|
|
120
|
+
# t.add_files 'test/unit/**/*_test.rb'
|
121
|
+
# t.add_files 'test/functional/**/*_test.rb'
|
122
|
+
# t.add_files 'test/integration/**/*_test.rb'
|
123
|
+
# t.generate_html = true # defaults to false
|
124
|
+
# t.generate_text = true # defaults to true
|
125
|
+
# end
|
126
|
+
class ProfileTask < Hydra::Task
|
127
|
+
# boolean: generate html output from ruby-prof
|
128
|
+
attr_accessor :generate_html
|
129
|
+
# boolean: generate text output from ruby-prof
|
130
|
+
attr_accessor :generate_text
|
131
|
+
|
132
|
+
# Create a new Hydra ProfileTask
|
133
|
+
def initialize(name = 'hydra:profile')
|
134
|
+
@name = name
|
135
|
+
@files = []
|
136
|
+
@verbose = false
|
137
|
+
@generate_html = false
|
138
|
+
@generate_text = true
|
139
|
+
|
140
|
+
yield self if block_given?
|
141
|
+
|
142
|
+
# Ensure we override rspec's at_exit
|
143
|
+
require 'hydra/spec/autorun_override'
|
144
|
+
|
145
|
+
@config = find_config_file
|
146
|
+
|
147
|
+
@opts = {
|
148
|
+
:verbose => @verbose,
|
149
|
+
:files => @files
|
150
|
+
}
|
151
|
+
define
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
# Create the rake task defined by this HydraTestTask
|
156
|
+
def define
|
157
|
+
desc "Hydra Test Profile" + (@name == :hydra ? "" : " for #{@name}")
|
158
|
+
task @name do
|
159
|
+
require 'ruby-prof'
|
160
|
+
RubyProf.start
|
161
|
+
|
162
|
+
runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
|
163
|
+
@files.each do |file|
|
164
|
+
$stdout.write runner.run_file(file)
|
165
|
+
$stdout.flush
|
166
|
+
end
|
167
|
+
|
168
|
+
$stdout.write "\nTests complete. Generating profiling output\n"
|
169
|
+
$stdout.flush
|
170
|
+
|
171
|
+
result = RubyProf.stop
|
172
|
+
|
173
|
+
if @generate_html
|
174
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
175
|
+
out = File.new("ruby-prof.html", 'w')
|
176
|
+
printer.print(out, :min_self => 0.05)
|
177
|
+
out.close
|
178
|
+
$stdout.write "Profiling data written to [ruby-prof.html]\n"
|
179
|
+
end
|
180
|
+
|
181
|
+
if @generate_text
|
182
|
+
printer = RubyProf::FlatPrinter.new(result)
|
183
|
+
out = File.new("ruby-prof.txt", 'w')
|
184
|
+
printer.print(out, :min_self => 0.05)
|
185
|
+
out.close
|
186
|
+
$stdout.write "Profiling data written to [ruby-prof.txt]\n"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Define a sync task that uses hydra to rsync the source tree under test to remote workers.
|
193
|
+
#
|
194
|
+
# This task is very useful to run before a remote db:reset task to make sure the db/schema.rb
|
195
|
+
# file is up to date on the remote workers.
|
196
|
+
#
|
197
|
+
# Hydra::SyncTask.new('hydra:sync') do |t|
|
198
|
+
# t.verbose = false # optionally set to true for lots of debug messages
|
199
|
+
# end
|
200
|
+
class SyncTask < Hydra::Task
|
201
|
+
|
202
|
+
# Create a new SyncTestTask
|
203
|
+
def initialize(name = :sync)
|
204
|
+
@name = name
|
205
|
+
@verbose = false
|
206
|
+
|
207
|
+
yield self if block_given?
|
208
|
+
|
209
|
+
@config = find_config_file
|
210
|
+
|
211
|
+
@opts = {
|
212
|
+
:verbose => @verbose
|
213
|
+
}
|
214
|
+
@opts.merge!(:config => @config) if @config
|
215
|
+
|
216
|
+
define
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
# Create the rake task defined by this HydraSyncTask
|
221
|
+
def define
|
222
|
+
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
223
|
+
task @name do
|
224
|
+
Hydra::Sync.sync_many(@opts)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Setup a task that will be run across all remote workers
|
230
|
+
# Hydra::RemoteTask.new('db:reset')
|
231
|
+
#
|
232
|
+
# Then you can run:
|
233
|
+
# rake hydra:remote:db:reset
|
234
|
+
class RemoteTask < Hydra::Task
|
235
|
+
include Open3
|
236
|
+
# Create a new hydra remote task with the given name.
|
237
|
+
# The task will be named hydra:remote:<name>
|
238
|
+
def initialize(name)
|
239
|
+
@name = name
|
240
|
+
yield self if block_given?
|
241
|
+
@config = find_config_file
|
242
|
+
if @config
|
243
|
+
define
|
244
|
+
else
|
245
|
+
task "hydra:remote:#{@name}" do ; end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
def define
|
251
|
+
desc "Run #{@name} remotely on all workers"
|
252
|
+
task "hydra:remote:#{@name}" do
|
253
|
+
config = YAML.load_file(@config)
|
254
|
+
environment = config.fetch('environment') { 'test' }
|
255
|
+
workers = config.fetch('workers') { [] }
|
256
|
+
workers = workers.select{|w| w['type'] == 'ssh'}
|
257
|
+
|
258
|
+
$stdout.write "==== Hydra Running #{@name} ====\n"
|
259
|
+
Thread.abort_on_exception = true
|
260
|
+
@listeners = []
|
261
|
+
@results = {}
|
262
|
+
workers.each do |worker|
|
263
|
+
@listeners << Thread.new do
|
264
|
+
begin
|
265
|
+
@results[worker] = if run_task(worker, environment)
|
266
|
+
"==== #{@name} passed on #{worker['connect']} ====\n"
|
267
|
+
else
|
268
|
+
"==== #{@name} failed on #{worker['connect']} ====\nPlease see above for more details.\n"
|
269
|
+
end
|
270
|
+
rescue
|
271
|
+
@results[worker] = "==== #{@name} failed for #{worker['connect']} ====\n#{$!.inspect}\n#{$!.backtrace.join("\n")}"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
@listeners.each{|l| l.join}
|
276
|
+
$stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n"
|
277
|
+
$stdout.write @results.values.join("\n")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def run_task worker, environment
|
282
|
+
$stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n"
|
283
|
+
ssh_opts = worker.fetch('ssh_opts') { '' }
|
284
|
+
writer, reader, error = popen3("ssh -tt #{ssh_opts} #{worker['connect']} ")
|
285
|
+
writer.write("cd #{worker['directory']}\n")
|
286
|
+
writer.write "echo BEGIN HYDRA\n"
|
287
|
+
writer.write("RAILS_ENV=#{environment} rake #{@name}\n")
|
288
|
+
writer.write "echo END HYDRA\n"
|
289
|
+
writer.write("exit\n")
|
290
|
+
writer.close
|
291
|
+
ignoring = true
|
292
|
+
passed = true
|
293
|
+
while line = reader.gets
|
294
|
+
line.chomp!
|
295
|
+
if line =~ /^rake aborted!$/
|
296
|
+
passed = false
|
297
|
+
end
|
298
|
+
if line =~ /echo END HYDRA$/
|
299
|
+
ignoring = true
|
300
|
+
end
|
301
|
+
$stdout.write "#{worker['connect']}: #{line}\n" unless ignoring
|
302
|
+
if line == 'BEGIN HYDRA'
|
303
|
+
ignoring = false
|
304
|
+
end
|
305
|
+
end
|
306
|
+
passed
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# A Hydra global task is a task that is run both locally and remotely.
|
311
|
+
#
|
312
|
+
# For example:
|
313
|
+
#
|
314
|
+
# Hydra::GlobalTask.new('db:reset')
|
315
|
+
#
|
316
|
+
# Allows you to run:
|
317
|
+
#
|
318
|
+
# rake hydra:db:reset
|
319
|
+
#
|
320
|
+
# Then, db:reset will be run locally and on all remote workers. This
|
321
|
+
# makes it easy to setup your workers and run tasks all in a row.
|
322
|
+
#
|
323
|
+
# For example:
|
324
|
+
#
|
325
|
+
# rake hydra:db:reset hydra:factories hydra:tests
|
326
|
+
#
|
327
|
+
# Assuming you setup hydra:db:reset and hydra:db:factories as global
|
328
|
+
# tasks and hydra:tests as a Hydra::TestTask for all your tests
|
329
|
+
class GlobalTask < Hydra::Task
|
330
|
+
def initialize(name)
|
331
|
+
@name = name
|
332
|
+
define
|
333
|
+
end
|
334
|
+
|
335
|
+
private
|
336
|
+
def define
|
337
|
+
Hydra::RemoteTask.new(@name)
|
338
|
+
desc "Run #{@name.to_s} Locally and Remotely across all Workers"
|
339
|
+
task "hydra:#{@name.to_s}" => [@name.to_s, "hydra:remote:#{@name.to_s}"]
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|