hydra 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|