hydra 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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