hydra 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,32 +25,93 @@ In your rakefile:
25
25
  t.add_files 'test/integration/**/*_test.rb'
26
26
  end
27
27
 
28
- Then you can run 'rake hydra'.
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 -p3022
56
+ connect: user@example.com
57
+ ssh_opts: -p3022
40
58
  directory: /absolute/path/to/project
41
59
  runners: 4
42
60
 
43
- The "connect" option is passed to SSH. So if you've setup an
44
- ssh config alias to a server, you can use that.
61
+ ==== Two Remote Quad Cores with Synchronization
45
62
 
46
- The "directory" option is the path for the project directory
47
- where the tests should be run.
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
- The "runners" option is how many processes will be running
50
- on the remote machine. It's best to pick the same number
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
@@ -11,7 +11,6 @@ begin
11
11
  gem.homepage = "http://github.com/ngauthier/hydra"
12
12
  gem.authors = ["Nick Gauthier"]
13
13
  gem.add_development_dependency "shoulda", "= 2.10.3"
14
- gem.add_dependency "net-ssh", "= 2.0.19"
15
14
  end
16
15
  Jeweler::GemcutterTasks.new
17
16
  rescue LoadError
data/TODO CHANGED
@@ -1,27 +1,10 @@
1
- IO selection configuration for master
2
- - allow pipe setup
3
- - allow ssh setup
1
+ = Hydra TODO
4
2
 
5
- YML configuration
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.8.0
1
+ 0.9.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{hydra}
8
- s.version = "0.8.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-06}
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
 
@@ -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. If there are no more files, this will shut the
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 options" }
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
 
@@ -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
@@ -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
- require file
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|
@@ -17,18 +17,21 @@ module Hydra #:nodoc:
17
17
  include Open3
18
18
  include Hydra::MessagingIO
19
19
 
20
- # Initialize new SSH connection. The single parameters is passed
21
- # directly to ssh for starting a connection. So you can do:
22
- # Hydra::SSH.new('localhost')
23
- # Hydra::SSH.new('user@server.com')
24
- # Hydra::SSH.new('-p 3022 user@server.com')
25
- # etc..
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
@@ -91,7 +91,6 @@ module Hydra #:nodoc:
91
91
  @listeners.each{|l| l.join }
92
92
  @io.close
93
93
  trace "Done processing messages"
94
- exit(0) # avoids test summaries
95
94
  end
96
95
 
97
96
  def process_messages_from_master
@@ -0,0 +1,8 @@
1
+ require 'test/unit'
2
+
3
+ class SyncTest < Test::Unit::TestCase
4
+ def test_truth
5
+ assert true
6
+ end
7
+ end
8
+
@@ -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.8.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-06 00:00:00 -05:00
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