hydra 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/TODO CHANGED
@@ -4,6 +4,17 @@ IO selection configuration for master
4
4
 
5
5
  YML configuration
6
6
 
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
17
+
7
18
  v0.6.0
8
19
 
9
20
  multitest backwards compatible
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.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.5.0"
8
+ s.version = "0.6.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-01-29}
12
+ s.date = %q{2010-02-03}
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 = [
@@ -36,9 +36,11 @@ Gem::Specification.new do |s|
36
36
  "lib/hydra/pipe.rb",
37
37
  "lib/hydra/runner.rb",
38
38
  "lib/hydra/ssh.rb",
39
+ "lib/hydra/stdio.rb",
39
40
  "lib/hydra/worker.rb",
40
41
  "test/fixtures/assert_true.rb",
41
42
  "test/fixtures/echo_the_dolphin.rb",
43
+ "test/fixtures/slow.rb",
42
44
  "test/fixtures/write_file.rb",
43
45
  "test/master_test.rb",
44
46
  "test/message_test.rb",
@@ -58,6 +60,7 @@ Gem::Specification.new do |s|
58
60
  "test/test_helper.rb",
59
61
  "test/ssh_test.rb",
60
62
  "test/fixtures/write_file.rb",
63
+ "test/fixtures/slow.rb",
61
64
  "test/fixtures/assert_true.rb",
62
65
  "test/fixtures/echo_the_dolphin.rb",
63
66
  "test/master_test.rb",
data/lib/hydra.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'hydra/pipe'
2
2
  require 'hydra/ssh'
3
+ require 'hydra/stdio'
3
4
  require 'hydra/message'
4
5
  require 'hydra/runner'
5
6
  require 'hydra/worker'
data/lib/hydra/master.rb CHANGED
@@ -18,7 +18,15 @@ module Hydra #:nodoc:
18
18
  @files = opts.fetch(:files) { [] }
19
19
  @workers = []
20
20
  @listeners = []
21
- boot_workers(opts.fetch(:workers) { [ {:runners => 1} ] } )
21
+ @verbose = opts.fetch(:verbose) { false }
22
+ # default is one worker that is configured to use a pipe with one runner
23
+ worker_cfg = opts.fetch(:workers) {
24
+ [ { :type => :local, :runners => 1} ]
25
+ }
26
+
27
+ $stdout.write "MASTER| Initialized\n" if @verbose
28
+
29
+ boot_workers worker_cfg
22
30
  process_messages
23
31
  end
24
32
 
@@ -39,25 +47,54 @@ module Hydra #:nodoc:
39
47
  private
40
48
 
41
49
  def boot_workers(workers)
42
- workers.each do |worker|
43
- pipe = Hydra::Pipe.new
44
- child = Process.fork do
45
- pipe.identify_as_child
46
- Hydra::Worker.new(:io => pipe, :runners => worker[:runners])
47
- end
48
- pipe.identify_as_parent
49
- @workers << { :pid => child, :io => pipe, :idle => false }
50
+ $stdout.write "MASTER| Booting workers\n" if @verbose
51
+ workers.select{|worker| worker[:type] == :local}.each do |worker|
52
+ boot_local_worker(worker)
53
+ end
54
+ workers.select{|worker| worker[:type] == :ssh}.each do |worker|
55
+ @workers << worker # will boot later, during the listening phase
56
+ end
57
+ end
58
+
59
+ def boot_local_worker(worker)
60
+ runners = worker.fetch(:runners) { raise "You must specify the number of runners" }
61
+ $stdout.write "MASTER| Booting local worker\n" if @verbose
62
+ pipe = Hydra::Pipe.new
63
+ child = Process.fork do
64
+ pipe.identify_as_child
65
+ Hydra::Worker.new(:io => pipe, :runners => runners)
50
66
  end
67
+ pipe.identify_as_parent
68
+ @workers << { :pid => child, :io => pipe, :idle => false, :type => :local }
69
+ end
70
+
71
+ def boot_ssh_worker(worker)
72
+ runners = worker.fetch(:runners) { raise "You must specify the number of runners" }
73
+ connect = worker.fetch(:connect) { raise "You must specify SSH connection options" }
74
+ directory = worker.fetch(:directory) { raise "You must specify a remote directory" }
75
+ command = worker.fetch(:command) {
76
+ "ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose});\""
77
+ }
78
+
79
+ $stdout.write "MASTER| Booting SSH worker\n" if @verbose
80
+ ssh = Hydra::SSH.new(connect, directory, command)
81
+ return { :io => ssh, :idle => false, :type => :ssh }
51
82
  end
52
83
 
53
84
  def process_messages
54
85
  Thread.abort_on_exception = true
55
86
 
87
+ $stdout.write "MASTER| Processing Messages\n" if @verbose
88
+ $stdout.write "MASTER| Workers: #{@workers}\n" if @verbose
56
89
  @workers.each do |worker|
57
90
  @listeners << Thread.new do
91
+ $stdout.write "MASTER| Listening to #{worker.inspect}\n" if @verbose
92
+ worker = boot_ssh_worker(worker) if worker.fetch(:type){ :local } == :ssh
58
93
  while true
59
94
  begin
95
+ $stdout.write "MASTER| listen....\n" if @verbose
60
96
  message = worker[:io].gets
97
+ $stdout.write "MASTER| got message: #{message}\n" if @verbose
61
98
  message.handle(self, worker) if message
62
99
  rescue IOError => ex
63
100
  $stderr.write "Master lost Worker [#{worker.inspect}]\n"
@@ -12,6 +12,9 @@ module Hydra #:nodoc:
12
12
  message = @reader.gets
13
13
  return nil unless message
14
14
  return Message.build(eval(message.chomp))
15
+ rescue SyntaxError, NameError
16
+ $stderr.write "Not a message: [#{message.inspect}]\n"
17
+ return gets
15
18
  end
16
19
 
17
20
  # Write a Message to the output IO object. It will automatically
data/lib/hydra/runner.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'test/unit'
2
+ require 'test/unit/testresult'
1
3
  module Hydra #:nodoc:
2
4
  # Hydra class responsible for running test files.
3
5
  #
@@ -15,13 +17,21 @@ module Hydra #:nodoc:
15
17
  @verbose = opts.fetch(:verbose) { false }
16
18
 
17
19
  Test::Unit.run = true
20
+ $stdout.sync = true
21
+ $stdout.write "RUNNER| Booted. Sending Request for file\n" if @verbose
18
22
 
19
23
  @io.write RequestFile.new
20
- process_messages
24
+ begin
25
+ process_messages
26
+ rescue => ex
27
+ $stdout.write "#{ex.to_s}\n" if @verbose
28
+ raise ex
29
+ end
21
30
  end
22
31
 
23
32
  # Run a test file and report the results
24
33
  def run_file(file)
34
+ $stdout.write "RUNNER| Running file: #{file}\n" if @verbose
25
35
  require file
26
36
  output = []
27
37
  @result = Test::Unit::TestResult.new
@@ -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/worker.rb CHANGED
@@ -124,7 +124,7 @@ module Hydra #:nodoc:
124
124
  message.handle(self, r)
125
125
  end
126
126
  rescue IOError => ex
127
- $stderr.write "Worker lost Runner [#{r.inspect}]\n"
127
+ $stderr.write "Worker lost Runner [#{r.inspect}]\n" if @verbose
128
128
  Thread.exit
129
129
  end
130
130
  end
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class WriteFileTest < Test::Unit::TestCase
4
+ def test_slow
5
+ sleep(2)
6
+ end
7
+ end
8
+
9
+
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
2
 
3
3
  class WriteFileTest < Test::Unit::TestCase
4
4
  def test_write_a_file
5
- File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'w') do |f|
5
+ File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'a') do |f|
6
6
  f.write "HYDRA"
7
7
  end
8
8
  end
data/test/master_test.rb CHANGED
@@ -11,9 +11,65 @@ class MasterTest < Test::Unit::TestCase
11
11
  end
12
12
 
13
13
  should "run a test" do
14
- m = Hydra::Master.new({
15
- :files => Array(test_file)
16
- })
14
+ Hydra::Master.new(
15
+ :files => [test_file]
16
+ )
17
+ assert File.exists?(target_file)
18
+ assert_equal "HYDRA", File.read(target_file)
19
+ end
20
+
21
+ should "run a test 6 times on 1 worker with 2 runners" do
22
+ Hydra::Master.new(
23
+ :files => [test_file]*6,
24
+ :local => {
25
+ :runners => 2
26
+ }
27
+ )
28
+ assert File.exists?(target_file)
29
+ assert_equal "HYDRA"*6, File.read(target_file)
30
+ end
31
+
32
+ # The test being run sleeps for 2 seconds. So, if this was run in
33
+ # series, it would take at least 20 seconds. This test ensures that
34
+ # in runs in less than that amount of time. Since there are 10
35
+ # runners to run the file 10 times, it should only take 2-4 seconds
36
+ # based on overhead.
37
+ should "run a slow test 10 times on 1 worker with 10 runners quickly" do
38
+ start = Time.now
39
+ Hydra::Master.new(
40
+ :files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
41
+ :workers => [
42
+ { :type => :local, :runners => 10 }
43
+ ]
44
+ )
45
+ finish = Time.now
46
+ assert (finish-start) < 15, "took #{finish-start} seconds"
47
+ end
48
+
49
+ should "run a slow test 10 times on 2 workers with 5 runners each quickly" do
50
+ start = Time.now
51
+ Hydra::Master.new(
52
+ :files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
53
+ :workers => [
54
+ { :type => :local, :runners => 5 },
55
+ { :type => :local, :runners => 5 }
56
+ ]
57
+ )
58
+ finish = Time.now
59
+ assert (finish-start) < 15, "took #{finish-start} seconds"
60
+ end
61
+
62
+
63
+ should "run a test via ssh" do
64
+ Hydra::Master.new(
65
+ :files => [test_file],
66
+ :workers => [{
67
+ :type => :ssh,
68
+ :connect => 'localhost',
69
+ :directory => File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
70
+ :runners => 1
71
+ }]
72
+ )
17
73
  assert File.exists?(target_file)
18
74
  assert_equal "HYDRA", File.read(target_file)
19
75
  end
data/test/runner_test.rb CHANGED
@@ -11,7 +11,7 @@ class RunnerTest < Test::Unit::TestCase
11
11
  end
12
12
 
13
13
 
14
- should "run a test" do
14
+ should "run a test in the foreground" do
15
15
  # flip it around to the parent is in the fork, this gives
16
16
  # us more direct control over the runner and proper test
17
17
  # coverage output
@@ -25,7 +25,7 @@ class RunnerTest < Test::Unit::TestCase
25
25
 
26
26
  # this flips the above test, so that the main process runs a bit of the parent
27
27
  # code, but only with minimal assertion
28
- should "be able to tell a runner to run a test" do
28
+ should "run a test in the background" do
29
29
  pipe = Hydra::Pipe.new
30
30
  child = Process.fork do
31
31
  run_the_runner(pipe)
@@ -33,6 +33,26 @@ class RunnerTest < Test::Unit::TestCase
33
33
  request_a_file_and_verify_completion(pipe)
34
34
  Process.wait(child)
35
35
  end
36
+
37
+ should "be able to run a runner over ssh" do
38
+ ssh = Hydra::SSH.new(
39
+ 'localhost',
40
+ File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
41
+ "ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Runner.new(:io => Hydra::Stdio.new);\""
42
+ )
43
+ assert ssh.gets.is_a?(Hydra::Messages::Runner::RequestFile)
44
+ ssh.write(Hydra::Messages::Worker::RunFile.new(:file => test_file))
45
+
46
+ # grab its response. This makes us wait for it to finish
47
+ response = ssh.gets
48
+
49
+ # tell it to shut down
50
+ ssh.write(Hydra::Messages::Worker::Shutdown.new)
51
+
52
+ # ensure it ran
53
+ assert File.exists?(target_file)
54
+ assert_equal "HYDRA", File.read(target_file)
55
+ end
36
56
  end
37
57
 
38
58
  module RunnerTestHelper
@@ -56,7 +76,7 @@ class RunnerTest < Test::Unit::TestCase
56
76
 
57
77
  def run_the_runner(pipe)
58
78
  pipe.identify_as_child
59
- Hydra::Runner.new({:io => pipe})
79
+ Hydra::Runner.new(:io => pipe)
60
80
  end
61
81
  end
62
82
  include RunnerTestHelper
data/test/test_helper.rb CHANGED
@@ -9,11 +9,11 @@ require 'hydra'
9
9
 
10
10
  class Test::Unit::TestCase
11
11
  def target_file
12
- File.join(Dir.tmpdir, 'hydra_test.txt')
12
+ File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt'))
13
13
  end
14
14
 
15
15
  def test_file
16
- File.join(File.dirname(__FILE__), 'fixtures', 'write_file.rb')
16
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file.rb'))
17
17
  end
18
18
  end
19
19
 
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.5.0
4
+ version: 0.6.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-01-29 00:00:00 -05:00
12
+ date: 2010-02-03 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -61,9 +61,11 @@ files:
61
61
  - lib/hydra/pipe.rb
62
62
  - lib/hydra/runner.rb
63
63
  - lib/hydra/ssh.rb
64
+ - lib/hydra/stdio.rb
64
65
  - lib/hydra/worker.rb
65
66
  - test/fixtures/assert_true.rb
66
67
  - test/fixtures/echo_the_dolphin.rb
68
+ - test/fixtures/slow.rb
67
69
  - test/fixtures/write_file.rb
68
70
  - test/master_test.rb
69
71
  - test/message_test.rb
@@ -105,6 +107,7 @@ test_files:
105
107
  - test/test_helper.rb
106
108
  - test/ssh_test.rb
107
109
  - test/fixtures/write_file.rb
110
+ - test/fixtures/slow.rb
108
111
  - test/fixtures/assert_true.rb
109
112
  - test/fixtures/echo_the_dolphin.rb
110
113
  - test/master_test.rb