hydra 0.6.0 → 0.7.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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.7.0
@@ -0,0 +1,6 @@
1
+ ---
2
+ directories_to_calculate: ---
3
+ - lib
4
+
5
+ test_directories_to_calculate: ---
6
+ - test
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{hydra}
8
- s.version = "0.6.0"
8
+ s.version = "0.7.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-03}
12
+ s.date = %q{2010-02-04}
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 = [
@@ -25,8 +25,10 @@ Gem::Specification.new do |s|
25
25
  "Rakefile",
26
26
  "TODO",
27
27
  "VERSION",
28
+ "caliper.yml",
28
29
  "hydra.gemspec",
29
30
  "lib/hydra.rb",
31
+ "lib/hydra/hash.rb",
30
32
  "lib/hydra/master.rb",
31
33
  "lib/hydra/message.rb",
32
34
  "lib/hydra/message/master_messages.rb",
@@ -37,8 +39,10 @@ Gem::Specification.new do |s|
37
39
  "lib/hydra/runner.rb",
38
40
  "lib/hydra/ssh.rb",
39
41
  "lib/hydra/stdio.rb",
42
+ "lib/hydra/trace.rb",
40
43
  "lib/hydra/worker.rb",
41
44
  "test/fixtures/assert_true.rb",
45
+ "test/fixtures/config.yml",
42
46
  "test/fixtures/echo_the_dolphin.rb",
43
47
  "test/fixtures/slow.rb",
44
48
  "test/fixtures/write_file.rb",
@@ -1,3 +1,4 @@
1
+ require 'hydra/trace'
1
2
  require 'hydra/pipe'
2
3
  require 'hydra/ssh'
3
4
  require 'hydra/stdio'
@@ -0,0 +1,16 @@
1
+ class Hash
2
+ # Stringify the keys in the hash. Returns a new hash.
3
+ def stringify_keys
4
+ inject({}) do |options, (key, value)|
5
+ options[key.to_s] = value
6
+ options
7
+ end
8
+ end
9
+ # Stringify the keys in the hash in place.
10
+ def stringify_keys!
11
+ keys.each do |key|
12
+ self[key.to_s] = delete(key)
13
+ end
14
+ self
15
+ end
16
+ end
@@ -1,9 +1,11 @@
1
+ require 'hydra/hash'
1
2
  module Hydra #:nodoc:
2
3
  # Hydra class responsible for delegate work down to workers.
3
4
  #
4
5
  # The Master is run once for any given testing session.
5
6
  class Master
6
7
  include Hydra::Messages::Master
8
+ traceable('MASTER')
7
9
  # Create a new Master
8
10
  #
9
11
  # Options:
@@ -15,16 +17,23 @@ module Hydra #:nodoc:
15
17
  # * An array of hashes. Each hash should be the configuration options
16
18
  # for a worker.
17
19
  def initialize(opts = { })
18
- @files = opts.fetch(:files) { [] }
20
+ opts.stringify_keys!
21
+ config_file = opts.delete('config') { nil }
22
+ if config_file
23
+ opts.merge!(YAML.load_file(config_file).stringify_keys!)
24
+ end
25
+ @files = opts.fetch('files') { [] }
26
+ @incomplete_files = @files.dup
19
27
  @workers = []
20
28
  @listeners = []
21
- @verbose = opts.fetch(:verbose) { false }
29
+ @verbose = opts.fetch('verbose') { false }
22
30
  # 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
- }
31
+ worker_cfg = opts.fetch('workers') { [ { 'type' => 'local', 'runners' => 1} ] }
26
32
 
27
- $stdout.write "MASTER| Initialized\n" if @verbose
33
+ trace "Initialized"
34
+ trace " Files: (#{@files.inspect})"
35
+ trace " Workers: (#{worker_cfg.inspect})"
36
+ trace " Verbose: (#{@verbose.inspect})"
28
37
 
29
38
  boot_workers worker_cfg
30
39
  process_messages
@@ -36,70 +45,93 @@ module Hydra #:nodoc:
36
45
  # worker down.
37
46
  def send_file(worker)
38
47
  f = @files.pop
39
- if f
40
- worker[:io].write(RunFile.new(:file => f))
48
+ worker[:io].write(RunFile.new(:file => f)) if f
49
+ end
50
+
51
+ # Process the results coming back from the worker.
52
+ def process_results(worker, message)
53
+ $stdout.write message.output
54
+ # only delete one
55
+ @incomplete_files.delete_at(@incomplete_files.index(message.file))
56
+ trace "#{@incomplete_files.size} Files Remaining"
57
+ if @incomplete_files.empty?
58
+ shutdown_all_workers
41
59
  else
42
- worker[:io].write(Shutdown.new)
43
- Thread.exit
60
+ send_file(worker)
44
61
  end
45
62
  end
46
63
 
47
64
  private
48
65
 
49
66
  def boot_workers(workers)
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
67
+ trace "Booting #{workers.size} workers"
68
+ workers.each do |worker|
69
+ worker.stringify_keys!
70
+ trace "worker opts #{worker.inspect}"
71
+ type = worker.fetch('type') { 'local' }
72
+ if type.to_s == 'local'
73
+ boot_local_worker(worker)
74
+ elsif type.to_s == 'ssh'
75
+ @workers << worker # will boot later, during the listening phase
76
+ else
77
+ raise "Worker type not recognized: (#{type.to_s})"
78
+ end
56
79
  end
57
80
  end
58
81
 
59
82
  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
83
+ runners = worker.fetch('runners') { raise "You must specify the number of runners" }
84
+ trace "Booting local worker"
62
85
  pipe = Hydra::Pipe.new
63
86
  child = Process.fork do
64
87
  pipe.identify_as_child
65
- Hydra::Worker.new(:io => pipe, :runners => runners)
88
+ Hydra::Worker.new(:io => pipe, :runners => runners, :verbose => @verbose)
66
89
  end
67
90
  pipe.identify_as_parent
68
91
  @workers << { :pid => child, :io => pipe, :idle => false, :type => :local }
69
92
  end
70
93
 
71
94
  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) {
95
+ runners = worker.fetch('runners') { raise "You must specify the number of runners" }
96
+ connect = worker.fetch('connect') { raise "You must specify SSH connection options" }
97
+ directory = worker.fetch('directory') { raise "You must specify a remote directory" }
98
+ command = worker.fetch('command') {
76
99
  "ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose});\""
77
100
  }
78
101
 
79
- $stdout.write "MASTER| Booting SSH worker\n" if @verbose
102
+ trace "Booting SSH worker"
80
103
  ssh = Hydra::SSH.new(connect, directory, command)
81
104
  return { :io => ssh, :idle => false, :type => :ssh }
82
105
  end
83
106
 
107
+ def shutdown_all_workers
108
+ trace "Shutting down all workers"
109
+ @workers.each do |worker|
110
+ worker[:io].write(Shutdown.new) if worker[:io]
111
+ worker[:io].close if worker[:io]
112
+ end
113
+ @listeners.each{|t| t.exit}
114
+ end
115
+
84
116
  def process_messages
85
117
  Thread.abort_on_exception = true
86
118
 
87
- $stdout.write "MASTER| Processing Messages\n" if @verbose
88
- $stdout.write "MASTER| Workers: #{@workers}\n" if @verbose
119
+ trace "Processing Messages"
120
+ trace "Workers: #{@workers.inspect}"
89
121
  @workers.each do |worker|
90
122
  @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
123
+ trace "Listening to #{worker.inspect}"
124
+ if worker.fetch('type') { 'local' }.to_s == 'ssh'
125
+ worker = boot_ssh_worker(worker)
126
+ @workers << worker
127
+ end
93
128
  while true
94
129
  begin
95
- $stdout.write "MASTER| listen....\n" if @verbose
96
130
  message = worker[:io].gets
97
- $stdout.write "MASTER| got message: #{message}\n" if @verbose
131
+ trace "got message: #{message}"
98
132
  message.handle(self, worker) if message
99
- rescue IOError => ex
100
- $stderr.write "Master lost Worker [#{worker.inspect}]\n"
101
- worker[:io].close
102
- @workers.delete(worker)
133
+ rescue IOError
134
+ trace "lost Worker [#{worker.inspect}]"
103
135
  Thread.exit
104
136
  end
105
137
  end
@@ -30,8 +30,7 @@ module Hydra #:nodoc:
30
30
  # Message relaying the results of a worker up to the master
31
31
  class Results < Hydra::Messages::Runner::Results
32
32
  def handle(master, worker) #:nodoc:
33
- $stdout.write output
34
- master.send_file(worker)
33
+ master.process_results(worker, self)
35
34
  end
36
35
  end
37
36
 
@@ -10,6 +10,7 @@ module Hydra #:nodoc:
10
10
  # of a machine.
11
11
  class Runner
12
12
  include Hydra::Messages::Runner
13
+ traceable('RUNNER')
13
14
  # Boot up a runner. It takes an IO object (generally a pipe from its
14
15
  # parent) to send it messages on which files to execute.
15
16
  def initialize(opts = {})
@@ -18,20 +19,20 @@ module Hydra #:nodoc:
18
19
 
19
20
  Test::Unit.run = true
20
21
  $stdout.sync = true
21
- $stdout.write "RUNNER| Booted. Sending Request for file\n" if @verbose
22
+ trace 'Booted. Sending Request for file'
22
23
 
23
24
  @io.write RequestFile.new
24
25
  begin
25
26
  process_messages
26
27
  rescue => ex
27
- $stdout.write "#{ex.to_s}\n" if @verbose
28
+ trace ex.to_s
28
29
  raise ex
29
30
  end
30
31
  end
31
32
 
32
33
  # Run a test file and report the results
33
34
  def run_file(file)
34
- $stdout.write "RUNNER| Running file: #{file}\n" if @verbose
35
+ trace "Running file: #{file}"
35
36
  require file
36
37
  output = []
37
38
  @result = Test::Unit::TestResult.new
@@ -60,20 +61,20 @@ module Hydra #:nodoc:
60
61
 
61
62
  # The runner will continually read messages and handle them.
62
63
  def process_messages
63
- $stdout.write "RUNNER| Processing Messages\n" if @verbose
64
+ trace "Processing Messages"
64
65
  @running = true
65
66
  while @running
66
67
  begin
67
68
  message = @io.gets
68
69
  if message
69
- $stdout.write "RUNNER| Received message from worker\n" if @verbose
70
- $stdout.write " | #{message.inspect}\n" if @verbose
70
+ trace "Received message from worker"
71
+ trace "\t#{message.inspect}"
71
72
  message.handle(self)
72
73
  else
73
74
  @io.write Ping.new
74
75
  end
75
76
  rescue IOError => ex
76
- $stderr.write "Runner lost Worker\n" if @verbose
77
+ trace "Runner lost Worker"
77
78
  @running = false
78
79
  end
79
80
  end
@@ -91,11 +92,9 @@ module Hydra #:nodoc:
91
92
  eval(c.first)
92
93
  end
93
94
  rescue NameError
94
- # $stderr.write "Could not load [#{c.first}] from [#{f}]\n"
95
- nil
95
+ trace "Could not load [#{c.first}] from [#{f}]"
96
96
  rescue SyntaxError
97
- # $stderr.write "Could not load [#{c.first}] from [#{f}]\n"
98
- nil
97
+ trace "Could not load [#{c.first}] from [#{f}]"
99
98
  end
100
99
  end
101
100
  return klasses.select{|k| k.respond_to? 'suite'}
@@ -0,0 +1,24 @@
1
+ module Hydra #:nodoc:
2
+ # Trace output when in verbose mode.
3
+ module Trace
4
+ module ClassMethods
5
+ # Make a class traceable. Takes one parameter,
6
+ # which is the prefix for the trace to identify this class
7
+ def traceable(prefix = self.class.to_s)
8
+ include Hydra::Trace::InstanceMethods
9
+ class << self; attr_accessor :_traceable_prefix; end
10
+ self._traceable_prefix = prefix
11
+ $stdout.sync = true
12
+ end
13
+ end
14
+
15
+ module InstanceMethods
16
+ # Trace some output with the class's prefix and a newline.
17
+ # Checks to ensure we're running verbosely.
18
+ def trace(str)
19
+ $stdout.write "#{self.class._traceable_prefix}| #{str}\n" if @verbose
20
+ end
21
+ end
22
+ end
23
+ end
24
+ Object.extend(Hydra::Trace::ClassMethods)
@@ -8,6 +8,7 @@ module Hydra #:nodoc:
8
8
  # network.
9
9
  class Worker
10
10
  include Hydra::Messages::Worker
11
+ traceable('WORKER')
11
12
  # Create a new worker.
12
13
  # * io: The IO object to use to communicate with the master
13
14
  # * num_runners: The number of runners to launch
@@ -52,10 +53,10 @@ module Hydra #:nodoc:
52
53
  # the worker to send shutdown messages to its runners.
53
54
  def shutdown
54
55
  @running = false
55
- $stdout.write "WORKER| Notifying #{@runners.size} Runners of Shutdown\n" if @verbose
56
+ trace "Notifying #{@runners.size} Runners of Shutdown"
56
57
  @runners.each do |r|
57
- $stdout.write "WORKER| Sending Shutdown to Runner\n" if @verbose
58
- $stdout.write " | #{r.inspect}\n" if @verbose
58
+ trace "Sending Shutdown to Runner"
59
+ trace "\t#{r.inspect}"
59
60
  r[:io].write(Shutdown.new)
60
61
  end
61
62
  Thread.exit
@@ -64,7 +65,7 @@ module Hydra #:nodoc:
64
65
  private
65
66
 
66
67
  def boot_runners(num_runners) #:nodoc:
67
- $stdout.write "WORKER| Booting #{num_runners} Runners\n" if @verbose
68
+ trace "Booting #{num_runners} Runners"
68
69
  num_runners.times do
69
70
  pipe = Hydra::Pipe.new
70
71
  child = Process.fork do
@@ -74,12 +75,12 @@ module Hydra #:nodoc:
74
75
  pipe.identify_as_parent
75
76
  @runners << { :pid => child, :io => pipe, :idle => false }
76
77
  end
77
- $stdout.write "WORKER| #{@runners.size} Runners booted\n" if @verbose
78
+ trace "#{@runners.size} Runners booted"
78
79
  end
79
80
 
80
81
  # Continuously process messages
81
82
  def process_messages #:nodoc:
82
- $stdout.write "WORKER| Processing Messages\n" if @verbose
83
+ trace "Processing Messages"
83
84
  @running = true
84
85
 
85
86
  Thread.abort_on_exception = true
@@ -89,7 +90,7 @@ module Hydra #:nodoc:
89
90
 
90
91
  @listeners.each{|l| l.join }
91
92
  @io.close
92
- $stdout.write "WORKER| Done processing messages\n" if @verbose
93
+ trace "Done processing messages"
93
94
  end
94
95
 
95
96
  def process_messages_from_master
@@ -98,14 +99,15 @@ module Hydra #:nodoc:
98
99
  begin
99
100
  message = @io.gets
100
101
  if message
101
- $stdout.write "WORKER| Received Message from Master\n" if @verbose
102
- $stdout.write " | #{message.inspect}\n" if @verbose
102
+ trace "Received Message from Master"
103
+ trace "\t#{message.inspect}"
103
104
  message.handle(self)
104
105
  else
106
+ trace "Nothing from Master, Pinging"
105
107
  @io.write Ping.new
106
108
  end
107
109
  rescue IOError => ex
108
- $stderr.write "Worker lost Master\n" if @verbose
110
+ trace "Worker lost Master"
109
111
  Thread.exit
110
112
  end
111
113
  end
@@ -119,12 +121,12 @@ module Hydra #:nodoc:
119
121
  begin
120
122
  message = r[:io].gets
121
123
  if message
122
- $stdout.write "WORKER| Received Message from Runner\n" if @verbose
123
- $stdout.write " | #{message.inspect}\n" if @verbose
124
+ trace "Received Message from Runner"
125
+ trace "\t#{message.inspect}"
124
126
  message.handle(self, r)
125
127
  end
126
128
  rescue IOError => ex
127
- $stderr.write "Worker lost Runner [#{r.inspect}]\n" if @verbose
129
+ trace "Worker lost Runner [#{r.inspect}]"
128
130
  Thread.exit
129
131
  end
130
132
  end
@@ -0,0 +1,4 @@
1
+ ---
2
+ workers:
3
+ - type: local
4
+ runners: 2
@@ -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_slow
5
- sleep(2)
5
+ sleep(5)
6
6
  end
7
7
  end
8
8
 
@@ -21,18 +21,16 @@ class MasterTest < Test::Unit::TestCase
21
21
  should "run a test 6 times on 1 worker with 2 runners" do
22
22
  Hydra::Master.new(
23
23
  :files => [test_file]*6,
24
- :local => {
25
- :runners => 2
26
- }
24
+ :workers => [ { :type => :local, :runners => 2 } ]
27
25
  )
28
26
  assert File.exists?(target_file)
29
27
  assert_equal "HYDRA"*6, File.read(target_file)
30
28
  end
31
29
 
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
30
+ # The test being run sleeps for 5 seconds. So, if this was run in
31
+ # series, it would take at least 50 seconds. This test ensures that
34
32
  # 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
33
+ # runners to run the file 10 times, it should only take 5-10 seconds
36
34
  # based on overhead.
37
35
  should "run a slow test 10 times on 1 worker with 10 runners quickly" do
38
36
  start = Time.now
@@ -43,7 +41,7 @@ class MasterTest < Test::Unit::TestCase
43
41
  ]
44
42
  )
45
43
  finish = Time.now
46
- assert (finish-start) < 15, "took #{finish-start} seconds"
44
+ assert (finish-start) < 30, "took #{finish-start} seconds"
47
45
  end
48
46
 
49
47
  should "run a slow test 10 times on 2 workers with 5 runners each quickly" do
@@ -73,5 +71,14 @@ class MasterTest < Test::Unit::TestCase
73
71
  assert File.exists?(target_file)
74
72
  assert_equal "HYDRA", File.read(target_file)
75
73
  end
74
+
75
+ should "run a test with config from a yaml file" do
76
+ Hydra::Master.new(
77
+ :files => [test_file],
78
+ :config => File.join(File.dirname(__FILE__), 'fixtures', 'config.yml')
79
+ )
80
+ assert File.exists?(target_file)
81
+ assert_equal "HYDRA", File.read(target_file)
82
+ end
76
83
  end
77
84
  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.6.0
4
+ version: 0.7.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-03 00:00:00 -05:00
12
+ date: 2010-02-04 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -50,8 +50,10 @@ files:
50
50
  - Rakefile
51
51
  - TODO
52
52
  - VERSION
53
+ - caliper.yml
53
54
  - hydra.gemspec
54
55
  - lib/hydra.rb
56
+ - lib/hydra/hash.rb
55
57
  - lib/hydra/master.rb
56
58
  - lib/hydra/message.rb
57
59
  - lib/hydra/message/master_messages.rb
@@ -62,8 +64,10 @@ files:
62
64
  - lib/hydra/runner.rb
63
65
  - lib/hydra/ssh.rb
64
66
  - lib/hydra/stdio.rb
67
+ - lib/hydra/trace.rb
65
68
  - lib/hydra/worker.rb
66
69
  - test/fixtures/assert_true.rb
70
+ - test/fixtures/config.yml
67
71
  - test/fixtures/echo_the_dolphin.rb
68
72
  - test/fixtures/slow.rb
69
73
  - test/fixtures/write_file.rb