hydra 0.8.0 → 0.9.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/README.rdoc +69 -8
- data/Rakefile +0 -1
- data/TODO +3 -20
- data/VERSION +1 -1
- data/hydra.gemspec +4 -5
- data/lib/hydra/master.rb +33 -5
- data/lib/hydra/messaging_io.rb +1 -0
- data/lib/hydra/runner.rb +7 -1
- data/lib/hydra/ssh.rb +9 -6
- data/lib/hydra/worker.rb +0 -1
- data/test/fixtures/sync_test.rb +8 -0
- data/test/master_test.rb +52 -1
- metadata +4 -12
data/README.rdoc
CHANGED
@@ -25,32 +25,93 @@ In your rakefile:
|
|
25
25
|
t.add_files 'test/integration/**/*_test.rb'
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
Run:
|
29
|
+
|
30
|
+
$ rake hydra
|
31
|
+
|
32
|
+
Hydra defaults to Single Core mode, so you may want to configure it
|
33
|
+
to use two (or more) of your cores if you have a multi-processing machine.
|
29
34
|
|
30
35
|
== Configuration
|
31
36
|
|
32
37
|
Place the config file in the main project directory as
|
33
38
|
'hydra.yml' or 'config/hydra.yml'.
|
34
39
|
|
40
|
+
=== Examples
|
41
|
+
|
42
|
+
==== Dual Core
|
43
|
+
|
44
|
+
workers:
|
45
|
+
- type: local
|
46
|
+
runners: 2
|
47
|
+
|
48
|
+
==== Dual Core, with a remote Quad Core server
|
49
|
+
|
50
|
+
The -p3022 tells it to connect on a different port
|
51
|
+
|
35
52
|
workers:
|
36
53
|
- type: local
|
37
54
|
runners: 2
|
38
55
|
- type: ssh
|
39
|
-
connect: user@example.com
|
56
|
+
connect: user@example.com
|
57
|
+
ssh_opts: -p3022
|
40
58
|
directory: /absolute/path/to/project
|
41
59
|
runners: 4
|
42
60
|
|
43
|
-
|
44
|
-
ssh config alias to a server, you can use that.
|
61
|
+
==== Two Remote Quad Cores with Synchronization
|
45
62
|
|
46
|
-
|
47
|
-
|
63
|
+
You can use the 'sync' configuration to allow rsync to synchronize
|
64
|
+
the local and remote directories every time you run hydra.
|
65
|
+
|
66
|
+
workers:
|
67
|
+
- type: ssh
|
68
|
+
connect: user@alpha.example.com
|
69
|
+
directory: /path/to/project/on/alpha/
|
70
|
+
runners: 4
|
71
|
+
- type: ssh
|
72
|
+
connect: user@beta.example.com
|
73
|
+
directory: /path/to/project/on/beta/
|
74
|
+
runners: 4
|
75
|
+
|
76
|
+
sync:
|
77
|
+
directory: /my/local/project/directory
|
78
|
+
exclude:
|
79
|
+
- tmp
|
80
|
+
- log
|
81
|
+
- doc
|
82
|
+
|
83
|
+
=== Workers Options
|
48
84
|
|
49
|
-
|
50
|
-
|
85
|
+
==== type
|
86
|
+
|
87
|
+
Either "local" or "ssh".
|
88
|
+
|
89
|
+
==== runners
|
90
|
+
|
91
|
+
The *runners* option is how many processes will be running
|
92
|
+
on the machine. It's best to pick the same number
|
51
93
|
as the number of cores on that machine (as well as your
|
52
94
|
own).
|
53
95
|
|
96
|
+
=== SSH Options
|
97
|
+
|
98
|
+
==== connect
|
99
|
+
|
100
|
+
The *connect* option is passed to SSH. So if you've setup an
|
101
|
+
ssh config alias to a server, you can use that. It is also
|
102
|
+
used in rsync, so you cannot use options.
|
103
|
+
|
104
|
+
==== ssh_opts
|
105
|
+
|
106
|
+
The *ssh_opts* option is passed to SSH and to Rsync's RSH so
|
107
|
+
that you can use the same ssh options for connecting and rsync.
|
108
|
+
Use ssh_opts to set the port or compression options.
|
109
|
+
|
110
|
+
==== directory
|
111
|
+
|
112
|
+
The *directory* option is the path for the project directory
|
113
|
+
where the tests should be run.
|
114
|
+
|
54
115
|
== Copyright
|
55
116
|
|
56
117
|
Copyright (c) 2010 Nick Gauthier. See LICENSE for details.
|
data/Rakefile
CHANGED
data/TODO
CHANGED
@@ -1,27 +1,10 @@
|
|
1
|
-
|
2
|
-
- allow pipe setup
|
3
|
-
- allow ssh setup
|
1
|
+
= Hydra TODO
|
4
2
|
|
5
|
-
|
3
|
+
== Setup
|
6
4
|
|
7
|
-
|
8
|
-
hydra:
|
9
|
-
workers:
|
10
|
-
- type: local
|
11
|
-
runners: 4
|
12
|
-
- type: ssh
|
13
|
-
connect: localhost
|
14
|
-
directory: /path/to/suite
|
15
|
-
[command: rake hydra:worker RUNNERS=1]
|
16
|
-
runners: 4
|
5
|
+
Provide a hydra:setup task to override to be run remotely before testing
|
17
6
|
|
18
|
-
v0.6.0
|
19
7
|
|
20
|
-
multitest backwards compatible
|
21
8
|
|
22
|
-
v0.7.0
|
23
9
|
|
24
|
-
???
|
25
|
-
|
26
|
-
v1.0.0
|
27
10
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.0
|
data/hydra.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{hydra}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.9.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Nick Gauthier"]
|
12
|
-
s.date = %q{2010-02-
|
12
|
+
s.date = %q{2010-02-09}
|
13
13
|
s.description = %q{Spread your tests over multiple machines to test your code faster.}
|
14
14
|
s.email = %q{nick@smartlogicsolutions.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -47,6 +47,7 @@ Gem::Specification.new do |s|
|
|
47
47
|
"test/fixtures/config.yml",
|
48
48
|
"test/fixtures/hello_world.rb",
|
49
49
|
"test/fixtures/slow.rb",
|
50
|
+
"test/fixtures/sync_test.rb",
|
50
51
|
"test/fixtures/write_file.rb",
|
51
52
|
"test/master_test.rb",
|
52
53
|
"test/message_test.rb",
|
@@ -67,6 +68,7 @@ Gem::Specification.new do |s|
|
|
67
68
|
"test/ssh_test.rb",
|
68
69
|
"test/fixtures/write_file.rb",
|
69
70
|
"test/fixtures/slow.rb",
|
71
|
+
"test/fixtures/sync_test.rb",
|
70
72
|
"test/fixtures/assert_true.rb",
|
71
73
|
"test/fixtures/hello_world.rb",
|
72
74
|
"test/master_test.rb",
|
@@ -81,14 +83,11 @@ Gem::Specification.new do |s|
|
|
81
83
|
|
82
84
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
83
85
|
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
|
84
|
-
s.add_runtime_dependency(%q<net-ssh>, ["= 2.0.19"])
|
85
86
|
else
|
86
87
|
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
87
|
-
s.add_dependency(%q<net-ssh>, ["= 2.0.19"])
|
88
88
|
end
|
89
89
|
else
|
90
90
|
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
91
|
-
s.add_dependency(%q<net-ssh>, ["= 2.0.19"])
|
92
91
|
end
|
93
92
|
end
|
94
93
|
|
data/lib/hydra/master.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'hydra/hash'
|
2
|
+
require 'open3'
|
2
3
|
module Hydra #:nodoc:
|
3
4
|
# Hydra class responsible for delegate work down to workers.
|
4
5
|
#
|
5
6
|
# The Master is run once for any given testing session.
|
6
7
|
class Master
|
7
8
|
include Hydra::Messages::Master
|
9
|
+
include Open3
|
8
10
|
traceable('MASTER')
|
9
11
|
# Create a new Master
|
10
12
|
#
|
@@ -22,12 +24,15 @@ module Hydra #:nodoc:
|
|
22
24
|
if config_file
|
23
25
|
opts.merge!(YAML.load_file(config_file).stringify_keys!)
|
24
26
|
end
|
25
|
-
@files = opts.fetch('files') {
|
27
|
+
@files = Array(opts.fetch('files') { nil })
|
28
|
+
raise "No files, nothing to do" if @files.empty?
|
26
29
|
@files.sort!{|a,b| File.size(b) <=> File.size(a)} # dumb heuristic
|
27
30
|
@incomplete_files = @files.dup
|
28
31
|
@workers = []
|
29
32
|
@listeners = []
|
30
33
|
@verbose = opts.fetch('verbose') { false }
|
34
|
+
@sync = opts.fetch('sync') { nil }
|
35
|
+
|
31
36
|
# default is one worker that is configured to use a pipe with one runner
|
32
37
|
worker_cfg = opts.fetch('workers') { [ { 'type' => 'local', 'runners' => 1} ] }
|
33
38
|
|
@@ -42,10 +47,10 @@ module Hydra #:nodoc:
|
|
42
47
|
|
43
48
|
# Message handling
|
44
49
|
|
45
|
-
# Send a file down to a worker.
|
46
|
-
# worker down.
|
50
|
+
# Send a file down to a worker.
|
47
51
|
def send_file(worker)
|
48
52
|
f = @files.pop
|
53
|
+
trace "Sending #{f.inspect}"
|
49
54
|
worker[:io].write(RunFile.new(:file => f)) if f
|
50
55
|
end
|
51
56
|
|
@@ -94,14 +99,37 @@ module Hydra #:nodoc:
|
|
94
99
|
|
95
100
|
def boot_ssh_worker(worker)
|
96
101
|
runners = worker.fetch('runners') { raise "You must specify the number of runners" }
|
97
|
-
connect = worker.fetch('connect') { raise "You must specify SSH connection
|
102
|
+
connect = worker.fetch('connect') { raise "You must specify an SSH connection target" }
|
103
|
+
ssh_opts = worker.fetch('ssh_opts') { "" }
|
98
104
|
directory = worker.fetch('directory') { raise "You must specify a remote directory" }
|
99
105
|
command = worker.fetch('command') {
|
100
106
|
"ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose});\""
|
101
107
|
}
|
102
108
|
|
109
|
+
if @sync
|
110
|
+
@sync.stringify_keys!
|
111
|
+
trace "Synchronizing with #{connect}\n\t#{@sync.inspect}"
|
112
|
+
local_dir = @sync.fetch('directory') {
|
113
|
+
raise "You must specify a synchronization directory"
|
114
|
+
}
|
115
|
+
exclude_paths = @sync.fetch('exclude') { [] }
|
116
|
+
exclude_opts = exclude_paths.inject(''){|memo, path| memo += "--exclude=#{path} "}
|
117
|
+
|
118
|
+
rsync_command = [
|
119
|
+
'rsync',
|
120
|
+
'-avz',
|
121
|
+
'--delete',
|
122
|
+
exclude_opts,
|
123
|
+
File.expand_path(local_dir)+'/',
|
124
|
+
"-e \"ssh #{ssh_opts}\"",
|
125
|
+
"#{connect}:#{directory}"
|
126
|
+
].join(" ")
|
127
|
+
trace rsync_command
|
128
|
+
trace `#{rsync_command}`
|
129
|
+
end
|
130
|
+
|
103
131
|
trace "Booting SSH worker"
|
104
|
-
ssh = Hydra::SSH.new(connect, directory, command)
|
132
|
+
ssh = Hydra::SSH.new("#{ssh_opts} #{connect}", directory, command)
|
105
133
|
return { :io => ssh, :idle => false, :type => :ssh }
|
106
134
|
end
|
107
135
|
|
data/lib/hydra/messaging_io.rb
CHANGED
@@ -13,6 +13,7 @@ module Hydra #:nodoc:
|
|
13
13
|
return nil unless message
|
14
14
|
return Message.build(eval(message.chomp))
|
15
15
|
rescue SyntaxError, NameError
|
16
|
+
# uncomment to help catch remote errors by seeing all traffic
|
16
17
|
#$stderr.write "Not a message: [#{message.inspect}]\n"
|
17
18
|
return gets
|
18
19
|
end
|
data/lib/hydra/runner.rb
CHANGED
@@ -33,7 +33,13 @@ module Hydra #:nodoc:
|
|
33
33
|
# Run a test file and report the results
|
34
34
|
def run_file(file)
|
35
35
|
trace "Running file: #{file}"
|
36
|
-
|
36
|
+
begin
|
37
|
+
require file
|
38
|
+
rescue LoadError => ex
|
39
|
+
trace "#{file} does not exist [#{ex.to_s}]"
|
40
|
+
@io.write Results.new(:output => ex.to_s, :file => file)
|
41
|
+
return
|
42
|
+
end
|
37
43
|
output = []
|
38
44
|
@result = Test::Unit::TestResult.new
|
39
45
|
@result.add_listener(Test::Unit::TestResult::FAULT) do |value|
|
data/lib/hydra/ssh.rb
CHANGED
@@ -17,18 +17,21 @@ module Hydra #:nodoc:
|
|
17
17
|
include Open3
|
18
18
|
include Hydra::MessagingIO
|
19
19
|
|
20
|
-
# Initialize new SSH connection.
|
21
|
-
# directly to ssh for starting a connection.
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
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.
|
26
28
|
def initialize(connection_options, directory, command)
|
27
29
|
@writer, @reader, @error = popen3("ssh -tt #{connection_options}")
|
28
30
|
@writer.write("cd #{directory}\n")
|
29
31
|
@writer.write(command+"\n")
|
30
32
|
end
|
31
33
|
|
34
|
+
# Close the SSH connection
|
32
35
|
def close
|
33
36
|
@writer.write "exit\n"
|
34
37
|
super
|
data/lib/hydra/worker.rb
CHANGED
data/test/master_test.rb
CHANGED
@@ -57,7 +57,6 @@ class MasterTest < Test::Unit::TestCase
|
|
57
57
|
assert (finish-start) < 15, "took #{finish-start} seconds"
|
58
58
|
end
|
59
59
|
|
60
|
-
|
61
60
|
should "run a test via ssh" do
|
62
61
|
Hydra::Master.new(
|
63
62
|
:files => [test_file],
|
@@ -80,5 +79,57 @@ class MasterTest < Test::Unit::TestCase
|
|
80
79
|
assert File.exists?(target_file)
|
81
80
|
assert_equal "HYDRA", File.read(target_file)
|
82
81
|
end
|
82
|
+
|
83
|
+
should "synchronize a test file over ssh with rsync" do
|
84
|
+
local = File.join(Dir.tmpdir, 'hydra', 'local')
|
85
|
+
remote = File.join(Dir.tmpdir, 'hydra', 'remote')
|
86
|
+
sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
|
87
|
+
[local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
|
88
|
+
|
89
|
+
# setup the folders:
|
90
|
+
# local:
|
91
|
+
# - test_a
|
92
|
+
# - test_c
|
93
|
+
# remote:
|
94
|
+
# - test_b
|
95
|
+
#
|
96
|
+
# add test_c to exludes
|
97
|
+
FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
|
98
|
+
FileUtils.cp(sync_test, File.join(local, 'test_c.rb'))
|
99
|
+
FileUtils.cp(sync_test, File.join(remote, 'test_b.rb'))
|
100
|
+
|
101
|
+
# ensure a is not on remote
|
102
|
+
assert !File.exists?(File.join(remote, 'test_a.rb')), "A should not be on remote"
|
103
|
+
# ensure c is not on remote
|
104
|
+
assert !File.exists?(File.join(remote, 'test_c.rb')), "C should not be on remote"
|
105
|
+
# ensure b is on remote
|
106
|
+
assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
|
107
|
+
|
108
|
+
# fake as if the test got run, so only the sync code is really being tested
|
109
|
+
fake_result = Hydra::Messages::Worker::Results.new(
|
110
|
+
:file => 'test_a.rb', :output => '.'
|
111
|
+
).serialize.inspect
|
112
|
+
|
113
|
+
Hydra::Master.new(
|
114
|
+
:files => ['test_a.rb'],
|
115
|
+
:workers => [{
|
116
|
+
:type => :ssh,
|
117
|
+
:connect => 'localhost',
|
118
|
+
:directory => remote,
|
119
|
+
:runners => 1,
|
120
|
+
:command => "ruby -e 'puts #{fake_result}' && exit"
|
121
|
+
}],
|
122
|
+
:sync => {
|
123
|
+
:directory => local,
|
124
|
+
:exclude => ['test_c.rb']
|
125
|
+
}
|
126
|
+
)
|
127
|
+
# ensure a is copied
|
128
|
+
assert File.exists?(File.join(remote, 'test_a.rb')), "A was not copied"
|
129
|
+
# ensure c is not copied
|
130
|
+
assert !File.exists?(File.join(remote, 'test_c.rb')), "C was copied, should be excluded"
|
131
|
+
# ensure b is deleted
|
132
|
+
assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
|
133
|
+
end
|
83
134
|
end
|
84
135
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hydra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Gauthier
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-02-
|
12
|
+
date: 2010-02-09 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,16 +22,6 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 2.10.3
|
24
24
|
version:
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: net-ssh
|
27
|
-
type: :runtime
|
28
|
-
version_requirement:
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 2.0.19
|
34
|
-
version:
|
35
25
|
description: Spread your tests over multiple machines to test your code faster.
|
36
26
|
email: nick@smartlogicsolutions.com
|
37
27
|
executables: []
|
@@ -72,6 +62,7 @@ files:
|
|
72
62
|
- test/fixtures/config.yml
|
73
63
|
- test/fixtures/hello_world.rb
|
74
64
|
- test/fixtures/slow.rb
|
65
|
+
- test/fixtures/sync_test.rb
|
75
66
|
- test/fixtures/write_file.rb
|
76
67
|
- test/master_test.rb
|
77
68
|
- test/message_test.rb
|
@@ -114,6 +105,7 @@ test_files:
|
|
114
105
|
- test/ssh_test.rb
|
115
106
|
- test/fixtures/write_file.rb
|
116
107
|
- test/fixtures/slow.rb
|
108
|
+
- test/fixtures/sync_test.rb
|
117
109
|
- test/fixtures/assert_true.rb
|
118
110
|
- test/fixtures/hello_world.rb
|
119
111
|
- test/master_test.rb
|