arturop-hydra 0.23.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +56 -0
- data/TODO +18 -0
- data/VERSION +1 -0
- data/caliper.yml +6 -0
- data/hydra-icon-64x64.png +0 -0
- data/hydra.gemspec +131 -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 +247 -0
- data/lib/hydra/message.rb +47 -0
- data/lib/hydra/message/master_messages.rb +19 -0
- data/lib/hydra/message/runner_messages.rb +46 -0
- data/lib/hydra/message/worker_messages.rb +52 -0
- data/lib/hydra/messaging_io.rb +49 -0
- data/lib/hydra/pipe.rb +61 -0
- data/lib/hydra/runner.rb +306 -0
- data/lib/hydra/runner_listener/abstract.rb +23 -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 +366 -0
- data/lib/hydra/tmpdir.rb +11 -0
- data/lib/hydra/trace.rb +24 -0
- data/lib/hydra/worker.rb +168 -0
- data/test/fixtures/assert_true.rb +7 -0
- data/test/fixtures/config.yml +4 -0
- data/test/fixtures/conflicting.rb +10 -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/hydra_worker_init.rb +2 -0
- data/test/fixtures/js_file.js +4 -0
- data/test/fixtures/json_data.json +4 -0
- data/test/fixtures/many_outputs_to_console.rb +9 -0
- data/test/fixtures/master_listeners.rb +10 -0
- data/test/fixtures/runner_listeners.rb +23 -0
- data/test/fixtures/slow.rb +9 -0
- data/test/fixtures/sync_test.rb +8 -0
- data/test/fixtures/task_test_config.yml +6 -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 +383 -0
- data/test/message_test.rb +31 -0
- data/test/pipe_test.rb +38 -0
- data/test/runner_test.rb +196 -0
- data/test/ssh_test.rb +25 -0
- data/test/sync_test.rb +113 -0
- data/test/task_test.rb +21 -0
- data/test/test_helper.rb +107 -0
- data/test/worker_test.rb +60 -0
- metadata +202 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
module RunnerListener #:nodoc:
|
3
|
+
# Abstract listener that implements all the events
|
4
|
+
# but does nothing.
|
5
|
+
class Abstract
|
6
|
+
# Create a new listener.
|
7
|
+
#
|
8
|
+
# Output: The IO object for outputting any information.
|
9
|
+
# Defaults to STDOUT, but you could pass a file in, or STDERR
|
10
|
+
def initialize(output = $stdout)
|
11
|
+
@output = output
|
12
|
+
end
|
13
|
+
|
14
|
+
# Fired by the runner just before requesting the first file
|
15
|
+
def runner_begin( runner )
|
16
|
+
end
|
17
|
+
|
18
|
+
# Fired by the runner just after stoping
|
19
|
+
def runner_end( runner )
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -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,366 @@
|
|
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
|
+
attr_accessor :environment
|
40
|
+
|
41
|
+
# Set to false if you don't want to show the total running time
|
42
|
+
attr_accessor :show_time
|
43
|
+
|
44
|
+
# Set to a valid file path if you want to save the output of the runners
|
45
|
+
# in a log file
|
46
|
+
attr_accessor :runner_log_file
|
47
|
+
|
48
|
+
#
|
49
|
+
# Search for the hydra config file
|
50
|
+
def find_config_file
|
51
|
+
@config ||= 'hydra.yml'
|
52
|
+
return @config if File.exists?(@config)
|
53
|
+
@config = File.join('config', 'hydra.yml')
|
54
|
+
return @config if File.exists?(@config)
|
55
|
+
@config = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add files to test by passing in a string to be run through Dir.glob.
|
59
|
+
# For example:
|
60
|
+
#
|
61
|
+
# t.add_files 'test/units/*.rb'
|
62
|
+
def add_files(pattern)
|
63
|
+
@files += Dir.glob(pattern)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# Define a test task that uses hydra to test the files.
|
69
|
+
#
|
70
|
+
# Hydra::TestTask.new('hydra') do |t|
|
71
|
+
# t.add_files 'test/unit/**/*_test.rb'
|
72
|
+
# t.add_files 'test/functional/**/*_test.rb'
|
73
|
+
# t.add_files 'test/integration/**/*_test.rb'
|
74
|
+
# t.verbose = false # optionally set to true for lots of debug messages
|
75
|
+
# t.autosort = false # disable automatic sorting based on runtime of tests
|
76
|
+
# end
|
77
|
+
class TestTask < Hydra::Task
|
78
|
+
|
79
|
+
# Create a new HydraTestTask
|
80
|
+
def initialize(name = :hydra)
|
81
|
+
@name = name
|
82
|
+
@files = []
|
83
|
+
@verbose = false
|
84
|
+
@autosort = true
|
85
|
+
@serial = false
|
86
|
+
@listeners = [Hydra::Listener::ProgressBar.new]
|
87
|
+
@show_time = true
|
88
|
+
|
89
|
+
yield self if block_given?
|
90
|
+
|
91
|
+
# Ensure we override rspec's at_exit
|
92
|
+
if defined?(RSpec)
|
93
|
+
RSpec::Core::Runner.disable_autorun!
|
94
|
+
end
|
95
|
+
|
96
|
+
unless @serial
|
97
|
+
@config = find_config_file
|
98
|
+
end
|
99
|
+
|
100
|
+
@opts = {
|
101
|
+
:verbose => @verbose,
|
102
|
+
:autosort => @autosort,
|
103
|
+
:files => @files,
|
104
|
+
:listeners => @listeners,
|
105
|
+
:environment => @environment,
|
106
|
+
:runner_log_file => @runner_log_file
|
107
|
+
}
|
108
|
+
if @config
|
109
|
+
@opts.merge!(:config => @config)
|
110
|
+
else
|
111
|
+
@opts.merge!(:workers => [{:type => :local, :runners => 1}])
|
112
|
+
end
|
113
|
+
|
114
|
+
define
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
# Create the rake task defined by this HydraTestTask
|
119
|
+
def define
|
120
|
+
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
121
|
+
task @name do
|
122
|
+
if Object.const_defined?('Rails') && Rails.env == 'development'
|
123
|
+
$stderr.puts %{WARNING: Rails Environment is "development". Make sure to set it properly (ex: "RAILS_ENV=test rake hydra")}
|
124
|
+
end
|
125
|
+
|
126
|
+
start = Time.now if @show_time
|
127
|
+
|
128
|
+
master = Hydra::Master.new(@opts)
|
129
|
+
|
130
|
+
$stdout.puts "\nFinished in #{'%.6f' % (Time.now - start)} seconds." if @show_time
|
131
|
+
|
132
|
+
unless master.failed_files.empty?
|
133
|
+
raise "Hydra: Not all tests passes"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Define a test task that uses hydra to profile your test files
|
140
|
+
#
|
141
|
+
# Hydra::ProfileTask.new('hydra:prof') do |t|
|
142
|
+
# t.add_files 'test/unit/**/*_test.rb'
|
143
|
+
# t.add_files 'test/functional/**/*_test.rb'
|
144
|
+
# t.add_files 'test/integration/**/*_test.rb'
|
145
|
+
# t.generate_html = true # defaults to false
|
146
|
+
# t.generate_text = true # defaults to true
|
147
|
+
# end
|
148
|
+
class ProfileTask < Hydra::Task
|
149
|
+
# boolean: generate html output from ruby-prof
|
150
|
+
attr_accessor :generate_html
|
151
|
+
# boolean: generate text output from ruby-prof
|
152
|
+
attr_accessor :generate_text
|
153
|
+
|
154
|
+
# Create a new Hydra ProfileTask
|
155
|
+
def initialize(name = 'hydra:profile')
|
156
|
+
@name = name
|
157
|
+
@files = []
|
158
|
+
@verbose = false
|
159
|
+
@generate_html = false
|
160
|
+
@generate_text = true
|
161
|
+
|
162
|
+
yield self if block_given?
|
163
|
+
|
164
|
+
# Ensure we override rspec's at_exit
|
165
|
+
require 'hydra/spec/autorun_override'
|
166
|
+
|
167
|
+
@config = find_config_file
|
168
|
+
|
169
|
+
@opts = {
|
170
|
+
:verbose => @verbose,
|
171
|
+
:files => @files
|
172
|
+
}
|
173
|
+
define
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
# Create the rake task defined by this HydraTestTask
|
178
|
+
def define
|
179
|
+
desc "Hydra Test Profile" + (@name == :hydra ? "" : " for #{@name}")
|
180
|
+
task @name do
|
181
|
+
require 'ruby-prof'
|
182
|
+
RubyProf.start
|
183
|
+
|
184
|
+
runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
|
185
|
+
@files.each do |file|
|
186
|
+
$stdout.write runner.run_file(file)
|
187
|
+
$stdout.flush
|
188
|
+
end
|
189
|
+
|
190
|
+
$stdout.write "\nTests complete. Generating profiling output\n"
|
191
|
+
$stdout.flush
|
192
|
+
|
193
|
+
result = RubyProf.stop
|
194
|
+
|
195
|
+
if @generate_html
|
196
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
197
|
+
out = File.new("ruby-prof.html", 'w')
|
198
|
+
printer.print(out, :min_self => 0.05)
|
199
|
+
out.close
|
200
|
+
$stdout.write "Profiling data written to [ruby-prof.html]\n"
|
201
|
+
end
|
202
|
+
|
203
|
+
if @generate_text
|
204
|
+
printer = RubyProf::FlatPrinter.new(result)
|
205
|
+
out = File.new("ruby-prof.txt", 'w')
|
206
|
+
printer.print(out, :min_self => 0.05)
|
207
|
+
out.close
|
208
|
+
$stdout.write "Profiling data written to [ruby-prof.txt]\n"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Define a sync task that uses hydra to rsync the source tree under test to remote workers.
|
215
|
+
#
|
216
|
+
# This task is very useful to run before a remote db:reset task to make sure the db/schema.rb
|
217
|
+
# file is up to date on the remote workers.
|
218
|
+
#
|
219
|
+
# Hydra::SyncTask.new('hydra:sync') do |t|
|
220
|
+
# t.verbose = false # optionally set to true for lots of debug messages
|
221
|
+
# end
|
222
|
+
class SyncTask < Hydra::Task
|
223
|
+
|
224
|
+
# Create a new SyncTestTask
|
225
|
+
def initialize(name = :sync)
|
226
|
+
@name = name
|
227
|
+
@verbose = false
|
228
|
+
|
229
|
+
yield self if block_given?
|
230
|
+
|
231
|
+
@config = find_config_file
|
232
|
+
|
233
|
+
@opts = {
|
234
|
+
:verbose => @verbose
|
235
|
+
}
|
236
|
+
@opts.merge!(:config => @config) if @config
|
237
|
+
|
238
|
+
define
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
# Create the rake task defined by this HydraSyncTask
|
243
|
+
def define
|
244
|
+
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
245
|
+
task @name do
|
246
|
+
Hydra::Sync.sync_many(@opts)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Setup a task that will be run across all remote workers
|
252
|
+
# Hydra::RemoteTask.new('db:reset')
|
253
|
+
#
|
254
|
+
# Then you can run:
|
255
|
+
# rake hydra:remote:db:reset
|
256
|
+
class RemoteTask < Hydra::Task
|
257
|
+
include Open3
|
258
|
+
# Create a new hydra remote task with the given name.
|
259
|
+
# The task will be named hydra:remote:<name>
|
260
|
+
def initialize(name, command=nil)
|
261
|
+
@name = name
|
262
|
+
@command = command
|
263
|
+
yield self if block_given?
|
264
|
+
@config = find_config_file
|
265
|
+
if @config
|
266
|
+
define
|
267
|
+
else
|
268
|
+
task "hydra:remote:#{@name}" do ; end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
def define
|
274
|
+
desc "Run #{@name} remotely on all workers"
|
275
|
+
task "hydra:remote:#{@name}" do
|
276
|
+
config = YAML.load_file(@config)
|
277
|
+
environment = config.fetch('environment') { 'test' }
|
278
|
+
workers = config.fetch('workers') { [] }
|
279
|
+
workers = workers.select{|w| w['type'] == 'ssh'}
|
280
|
+
@command = "RAILS_ENV=#{environment} rake #{@name}" unless @command
|
281
|
+
|
282
|
+
$stdout.write "==== Hydra Running #{@name} ====\n"
|
283
|
+
Thread.abort_on_exception = true
|
284
|
+
@listeners = []
|
285
|
+
@results = {}
|
286
|
+
workers.each do |worker|
|
287
|
+
@listeners << Thread.new do
|
288
|
+
begin
|
289
|
+
@results[worker] = if run_command(worker, @command)
|
290
|
+
"==== #{@name} passed on #{worker['connect']} ====\n"
|
291
|
+
else
|
292
|
+
"==== #{@name} failed on #{worker['connect']} ====\nPlease see above for more details.\n"
|
293
|
+
end
|
294
|
+
rescue
|
295
|
+
@results[worker] = "==== #{@name} failed for #{worker['connect']} ====\n#{$!.inspect}\n#{$!.backtrace.join("\n")}"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
@listeners.each{|l| l.join}
|
300
|
+
$stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n"
|
301
|
+
$stdout.write @results.values.join("\n")
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def run_command worker, command
|
306
|
+
$stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n"
|
307
|
+
ssh_opts = worker.fetch('ssh_opts') { '' }
|
308
|
+
writer, reader, error = popen3("ssh -tt #{ssh_opts} #{worker['connect']} ")
|
309
|
+
writer.write("cd #{worker['directory']}\n")
|
310
|
+
writer.write "echo BEGIN HYDRA\n"
|
311
|
+
writer.write(command + "\r")
|
312
|
+
writer.write "echo END HYDRA\n"
|
313
|
+
writer.write("exit\n")
|
314
|
+
writer.close
|
315
|
+
ignoring = true
|
316
|
+
passed = true
|
317
|
+
while line = reader.gets
|
318
|
+
line.chomp!
|
319
|
+
if line =~ /^rake aborted!$/
|
320
|
+
passed = false
|
321
|
+
end
|
322
|
+
if line =~ /echo END HYDRA$/
|
323
|
+
ignoring = true
|
324
|
+
end
|
325
|
+
$stdout.write "#{worker['connect']}: #{line}\n" unless ignoring
|
326
|
+
if line == 'BEGIN HYDRA'
|
327
|
+
ignoring = false
|
328
|
+
end
|
329
|
+
end
|
330
|
+
passed
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# A Hydra global task is a task that is run both locally and remotely.
|
335
|
+
#
|
336
|
+
# For example:
|
337
|
+
#
|
338
|
+
# Hydra::GlobalTask.new('db:reset')
|
339
|
+
#
|
340
|
+
# Allows you to run:
|
341
|
+
#
|
342
|
+
# rake hydra:db:reset
|
343
|
+
#
|
344
|
+
# Then, db:reset will be run locally and on all remote workers. This
|
345
|
+
# makes it easy to setup your workers and run tasks all in a row.
|
346
|
+
#
|
347
|
+
# For example:
|
348
|
+
#
|
349
|
+
# rake hydra:db:reset hydra:factories hydra:tests
|
350
|
+
#
|
351
|
+
# Assuming you setup hydra:db:reset and hydra:db:factories as global
|
352
|
+
# tasks and hydra:tests as a Hydra::TestTask for all your tests
|
353
|
+
class GlobalTask < Hydra::Task
|
354
|
+
def initialize(name)
|
355
|
+
@name = name
|
356
|
+
define
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
def define
|
361
|
+
Hydra::RemoteTask.new(@name)
|
362
|
+
desc "Run #{@name.to_s} Locally and Remotely across all Workers"
|
363
|
+
task "hydra:#{@name.to_s}" => [@name.to_s, "hydra:remote:#{@name.to_s}"]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|