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.
@@ -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