hydra 0.5.0 → 0.6.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/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