causes-hydra 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +56 -0
  6. data/TODO +18 -0
  7. data/VERSION +1 -0
  8. data/bin/warmsnake.rb +76 -0
  9. data/caliper.yml +6 -0
  10. data/hydra-icon-64x64.png +0 -0
  11. data/hydra.gemspec +130 -0
  12. data/hydra_gray.png +0 -0
  13. data/lib/hydra.rb +16 -0
  14. data/lib/hydra/cucumber/formatter.rb +29 -0
  15. data/lib/hydra/hash.rb +16 -0
  16. data/lib/hydra/js/lint.js +5150 -0
  17. data/lib/hydra/listener/abstract.rb +39 -0
  18. data/lib/hydra/listener/minimal_output.rb +24 -0
  19. data/lib/hydra/listener/notifier.rb +17 -0
  20. data/lib/hydra/listener/progress_bar.rb +48 -0
  21. data/lib/hydra/listener/report_generator.rb +30 -0
  22. data/lib/hydra/master.rb +249 -0
  23. data/lib/hydra/message.rb +47 -0
  24. data/lib/hydra/message/master_messages.rb +19 -0
  25. data/lib/hydra/message/runner_messages.rb +52 -0
  26. data/lib/hydra/message/worker_messages.rb +52 -0
  27. data/lib/hydra/messaging_io.rb +46 -0
  28. data/lib/hydra/pipe.rb +61 -0
  29. data/lib/hydra/runner.rb +305 -0
  30. data/lib/hydra/safe_fork.rb +31 -0
  31. data/lib/hydra/spec/autorun_override.rb +3 -0
  32. data/lib/hydra/spec/hydra_formatter.rb +26 -0
  33. data/lib/hydra/ssh.rb +41 -0
  34. data/lib/hydra/stdio.rb +16 -0
  35. data/lib/hydra/sync.rb +99 -0
  36. data/lib/hydra/tasks.rb +342 -0
  37. data/lib/hydra/trace.rb +24 -0
  38. data/lib/hydra/worker.rb +150 -0
  39. data/test/fixtures/assert_true.rb +7 -0
  40. data/test/fixtures/config.yml +4 -0
  41. data/test/fixtures/features/step_definitions.rb +21 -0
  42. data/test/fixtures/features/write_alternate_file.feature +7 -0
  43. data/test/fixtures/features/write_file.feature +7 -0
  44. data/test/fixtures/hello_world.rb +3 -0
  45. data/test/fixtures/js_file.js +4 -0
  46. data/test/fixtures/json_data.json +4 -0
  47. data/test/fixtures/slow.rb +9 -0
  48. data/test/fixtures/sync_test.rb +8 -0
  49. data/test/fixtures/write_file.rb +10 -0
  50. data/test/fixtures/write_file_alternate_spec.rb +10 -0
  51. data/test/fixtures/write_file_spec.rb +9 -0
  52. data/test/fixtures/write_file_with_pending_spec.rb +11 -0
  53. data/test/master_test.rb +152 -0
  54. data/test/message_test.rb +31 -0
  55. data/test/pipe_test.rb +38 -0
  56. data/test/runner_test.rb +153 -0
  57. data/test/ssh_test.rb +14 -0
  58. data/test/sync_test.rb +113 -0
  59. data/test/test_helper.rb +68 -0
  60. data/test/worker_test.rb +60 -0
  61. metadata +209 -0
@@ -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 "#{Time.now.to_f} #{self.class._traceable_prefix}| #{str}\n" if @verbose
20
+ end
21
+ end
22
+ end
23
+ end
24
+ Object.extend(Hydra::Trace::ClassMethods)
@@ -0,0 +1,150 @@
1
+ module Hydra #:nodoc:
2
+ # Hydra class responsible to dispatching runners and communicating with the master.
3
+ #
4
+ # The Worker is never run directly by a user. Workers are created by a
5
+ # Master to delegate to Runners.
6
+ #
7
+ # The general convention is to have one Worker per machine on a distributed
8
+ # network.
9
+ class Worker
10
+ include Hydra::Messages::Worker
11
+ traceable('WORKER')
12
+
13
+ attr_reader :runners
14
+ # Create a new worker.
15
+ # * io: The IO object to use to communicate with the master
16
+ # * num_runners: The number of runners to launch
17
+ def initialize(opts = {})
18
+ @verbose = opts.fetch(:verbose) { false }
19
+ @io = opts.fetch(:io) { raise "No IO Object" }
20
+ @runners = []
21
+ @listeners = []
22
+
23
+ boot_runners(opts.fetch(:runners) { 1 })
24
+ @io.write(Hydra::Messages::Worker::WorkerBegin.new)
25
+
26
+ process_messages
27
+
28
+ @runners.each{|r| Process.wait r[:pid] }
29
+ end
30
+
31
+
32
+ # message handling methods
33
+
34
+ # When a runner wants a file, it hits this method with a message.
35
+ # Then the worker bubbles the file request up to the master.
36
+ def request_file(message, runner)
37
+ @io.write(RequestFile.new)
38
+ runner[:idle] = true
39
+ end
40
+
41
+ # When the master sends a file down to the worker, it hits this
42
+ # method. Then the worker delegates the file down to a runner.
43
+ def delegate_file(message)
44
+ runner = idle_runner
45
+ runner[:idle] = false
46
+ runner[:io].write(RunFile.new(eval(message.serialize)))
47
+ end
48
+
49
+ # When a runner finishes, it sends the results up to the worker. Then the
50
+ # worker sends the results up to the master.
51
+ def relay_results(message, runner)
52
+ runner[:idle] = true
53
+ @io.write(Results.new(eval(message.serialize)))
54
+ end
55
+
56
+ # When a master issues a shutdown order, it hits this method, which causes
57
+ # the worker to send shutdown messages to its runners.
58
+ def shutdown
59
+ @running = false
60
+ trace "Notifying #{@runners.size} Runners of Shutdown"
61
+ @runners.each do |r|
62
+ trace "Sending Shutdown to Runner"
63
+ trace "\t#{r.inspect}"
64
+ r[:io].write(Shutdown.new)
65
+ end
66
+ Thread.exit
67
+ end
68
+
69
+ private
70
+
71
+ def boot_runners(num_runners) #:nodoc:
72
+ trace "Booting #{num_runners} Runners"
73
+ num_runners.times do
74
+ pipe = Hydra::Pipe.new
75
+ child = SafeFork.fork do
76
+ pipe.identify_as_child
77
+ Hydra::Runner.new(:io => pipe, :verbose => @verbose)
78
+ end
79
+ pipe.identify_as_parent
80
+ @runners << { :pid => child, :io => pipe, :idle => false }
81
+ end
82
+ trace "#{@runners.size} Runners booted"
83
+ end
84
+
85
+ # Continuously process messages
86
+ def process_messages #:nodoc:
87
+ trace "Processing Messages"
88
+ @running = true
89
+
90
+ Thread.abort_on_exception = true
91
+
92
+ process_messages_from_master
93
+ process_messages_from_runners
94
+
95
+ @listeners.each{|l| l.join }
96
+ @io.close
97
+ trace "Done processing messages"
98
+ end
99
+
100
+ def process_messages_from_master
101
+ @listeners << Thread.new do
102
+ while @running
103
+ begin
104
+ message = @io.gets
105
+ if message and !message.class.to_s.index("Master").nil?
106
+ trace "Received Message from Master"
107
+ trace "\t#{message.inspect}"
108
+ message.handle(self)
109
+ else
110
+ trace "Nothing from Master, Pinging"
111
+ @io.write Ping.new
112
+ end
113
+ rescue IOError => ex
114
+ trace "Worker lost Master"
115
+ Thread.exit
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ def process_messages_from_runners
122
+ @runners.each do |r|
123
+ @listeners << Thread.new do
124
+ while @running
125
+ begin
126
+ message = r[:io].gets
127
+ if message and !message.class.to_s.index("Runner").nil?
128
+ trace "Received Message from Runner"
129
+ trace "\t#{message.inspect}"
130
+ message.handle(self, r)
131
+ end
132
+ rescue IOError => ex
133
+ trace "Worker lost Runner [#{r.inspect}]"
134
+ Thread.exit
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ # Get the next idle runner
142
+ def idle_runner #:nodoc:
143
+ idle_r = nil
144
+ while idle_r.nil?
145
+ idle_r = @runners.detect{|runner| runner[:idle]}
146
+ end
147
+ return idle_r
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,7 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class AssertTrueTest < Test::Unit::TestCase
4
+ should "be true" do
5
+ assert true
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ workers:
3
+ - type: local
4
+ runners: 2
@@ -0,0 +1,21 @@
1
+ Given /^a target file$/ do
2
+ @target_file = File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt'))
3
+ end
4
+
5
+ Given /^an alternate target file$/ do
6
+ @target_file = File.expand_path(File.join(Dir.tmpdir, 'alternate_hydra_test.txt'))
7
+ end
8
+
9
+ When /^I write "([^\"]*)" to the file$/ do |text|
10
+ f = File.new(@target_file, 'w')
11
+ f.write text
12
+ f.flush
13
+ f.close
14
+ end
15
+
16
+ Then /^"([^\"]*)" should be written in the file$/ do |text|
17
+ f = File.new(@target_file, 'r')
18
+ raise 'Did not write to file' unless text == f.read
19
+ f.close
20
+ end
21
+
@@ -0,0 +1,7 @@
1
+ Feature: Write a file
2
+
3
+ Scenario: Write to hydra_test.txt
4
+ Given an alternate target file
5
+ When I write "HYDRA" to the file
6
+ Then "HYDRA" should be written in the file
7
+
@@ -0,0 +1,7 @@
1
+ Feature: Write a file
2
+
3
+ Scenario: Write to hydra_test.txt
4
+ Given a target file
5
+ When I write "HYDRA" to the file
6
+ Then "HYDRA" should be written in the file
7
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ $stdout.write "{:class=>Hydra::Messages::TestMessage, :text=>\"Hello World\"}\n"
3
+ $stdout.flush
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ var thisvar;
3
+ var thatvar
4
+
@@ -0,0 +1,4 @@
1
+ {
2
+ "var1": "something",
3
+ "var2": "trailing comma",
4
+ }
@@ -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(5)
6
+ end
7
+ end
8
+
9
+
@@ -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
+
@@ -0,0 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class WriteFileTest < Test::Unit::TestCase
4
+ def test_write_a_file
5
+ File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ require 'tmpdir'
2
+ require 'rspec'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.tmpdir, 'alternate_hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,9 @@
1
+ require 'tmpdir'
2
+ require 'rspec'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'tmpdir'
2
+ require 'rspec'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ it 'could do so much more' # pending spec
10
+ end
11
+
@@ -0,0 +1,152 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class MasterTest < Test::Unit::TestCase
4
+ context "with a file to test and a destination to verify" do
5
+ setup do
6
+ # avoid having other tests interfering with us
7
+ sleep(0.2)
8
+ FileUtils.rm_f(target_file)
9
+ end
10
+
11
+ teardown do
12
+ FileUtils.rm_f(target_file)
13
+ end
14
+
15
+ should "run a test" do
16
+ Hydra::Master.new(
17
+ :files => [test_file]
18
+ )
19
+ assert File.exists?(target_file)
20
+ assert_equal "HYDRA", File.read(target_file)
21
+ end
22
+
23
+ should "run a spec with pending examples" do
24
+ progress_bar = Hydra::Listener::ProgressBar.new(StringIO.new)
25
+ Hydra::Master.new(
26
+ :files => [rspec_file_with_pending],
27
+ :listeners => [progress_bar]
28
+ )
29
+ assert File.exists?(target_file)
30
+ assert_equal "HYDRA", File.read(target_file)
31
+ assert_equal false, progress_bar.instance_variable_get('@errors')
32
+ end
33
+
34
+ should "generate a report" do
35
+ Hydra::Master.new(:files => [test_file])
36
+ assert File.exists?(target_file)
37
+ assert_equal "HYDRA", File.read(target_file)
38
+ report_file = File.join(Dir.tmpdir, 'hydra_heuristics.yml')
39
+ assert File.exists?(report_file)
40
+ assert report = YAML.load_file(report_file)
41
+ assert_not_nil report[test_file]
42
+ end
43
+
44
+ should "run a test 6 times on 1 worker with 2 runners" do
45
+ Hydra::Master.new(
46
+ :files => [test_file]*6,
47
+ :workers => [ { :type => :local, :runners => 2 } ]
48
+ )
49
+ assert File.exists?(target_file)
50
+ assert_equal "HYDRA"*6, File.read(target_file)
51
+ end
52
+
53
+ # The test being run sleeps for 5 seconds. So, if this was run in
54
+ # series, it would take at least 50 seconds. This test ensures that
55
+ # in runs in less than that amount of time. Since there are 10
56
+ # runners to run the file 10 times, it should only take 5-10 seconds
57
+ # based on overhead.
58
+ should "run a slow test 10 times on 1 worker with 10 runners quickly" do
59
+ start = Time.now
60
+ Hydra::Master.new(
61
+ :files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
62
+ :workers => [
63
+ { :type => :local, :runners => 10 }
64
+ ]
65
+ )
66
+ finish = Time.now
67
+ assert (finish-start) < 30, "took #{finish-start} seconds"
68
+ end
69
+
70
+ should "run a slow test 10 times on 2 workers with 5 runners each quickly" do
71
+ start = Time.now
72
+ Hydra::Master.new(
73
+ :files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
74
+ :workers => [
75
+ { :type => :local, :runners => 5 },
76
+ { :type => :local, :runners => 5 }
77
+ ]
78
+ )
79
+ finish = Time.now
80
+ assert (finish-start) < 15, "took #{finish-start} seconds"
81
+ end
82
+
83
+ should "run a test via ssh" do
84
+ Hydra::Master.new(
85
+ :files => [test_file],
86
+ :workers => [{
87
+ :type => :ssh,
88
+ :connect => 'localhost',
89
+ :directory => File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
90
+ :runners => 1
91
+ }]
92
+ )
93
+ assert File.exists?(target_file)
94
+ assert_equal "HYDRA", File.read(target_file)
95
+ end
96
+
97
+ should "run a test with config from a yaml file" do
98
+ Hydra::Master.new(
99
+ :files => [test_file],
100
+ :config => File.join(File.dirname(__FILE__), 'fixtures', 'config.yml')
101
+ )
102
+ assert File.exists?(target_file)
103
+ assert_equal "HYDRA", File.read(target_file)
104
+ end
105
+
106
+ should "synchronize a test file over ssh with rsync" do
107
+ local = File.join(Dir.tmpdir, 'hydra', 'local')
108
+ remote = File.join(Dir.tmpdir, 'hydra', 'remote')
109
+ sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
110
+ [local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
111
+
112
+ # setup the folders:
113
+ # local:
114
+ # - test_a
115
+ # - test_c
116
+ # remote:
117
+ # - test_b
118
+ #
119
+ # add test_c to exludes
120
+ FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
121
+ FileUtils.cp(sync_test, File.join(local, 'test_c.rb'))
122
+ FileUtils.cp(sync_test, File.join(remote, 'test_b.rb'))
123
+
124
+ # ensure a is not on remote
125
+ assert !File.exists?(File.join(remote, 'test_a.rb')), "A should not be on remote"
126
+ # ensure c is not on remote
127
+ assert !File.exists?(File.join(remote, 'test_c.rb')), "C should not be on remote"
128
+ # ensure b is on remote
129
+ assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
130
+
131
+ Hydra::Master.new(
132
+ :files => ['test_a.rb'],
133
+ :workers => [{
134
+ :type => :ssh,
135
+ :connect => 'localhost',
136
+ :directory => remote,
137
+ :runners => 1
138
+ }],
139
+ :sync => {
140
+ :directory => local,
141
+ :exclude => ['test_c.rb']
142
+ }
143
+ )
144
+ # ensure a is copied
145
+ assert File.exists?(File.join(remote, 'test_a.rb')), "A was not copied"
146
+ # ensure c is not copied
147
+ assert !File.exists?(File.join(remote, 'test_c.rb')), "C was copied, should be excluded"
148
+ # ensure b is deleted
149
+ assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
150
+ end
151
+ end
152
+ end