causes-hydra 0.21.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.
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,31 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class MessageTest < Test::Unit::TestCase
4
+ class MyMessage < Hydra::Message
5
+ attr_accessor :my_var
6
+ def serialize
7
+ super(:my_var => @my_var)
8
+ end
9
+ end
10
+
11
+ context "with a message" do
12
+ setup do
13
+ @m = MyMessage.new(:my_var => 'my value')
14
+ end
15
+ should "set values" do
16
+ assert_equal 'my value', @m.my_var
17
+ end
18
+ should "serialize" do
19
+ assert_equal(
20
+ {:class=>MyMessage, :my_var=>"my value"},
21
+ eval(@m.serialize)
22
+ )
23
+ end
24
+ should "build from serialization" do
25
+ assert_equal(
26
+ @m.my_var,
27
+ Hydra::Message.build(eval(@m.serialize)).my_var
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class PipeTest < Test::Unit::TestCase
4
+ context "a pipe" do
5
+ setup do
6
+ @pipe = Hydra::Pipe.new
7
+ end
8
+ teardown do
9
+ @pipe.close
10
+ end
11
+ should "be able to write messages" do
12
+ child = Process.fork do
13
+ @pipe.identify_as_child
14
+ assert_equal "Test Message", @pipe.gets.text
15
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "Message Received")
16
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "Second Message")
17
+ end
18
+ @pipe.identify_as_parent
19
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
20
+ assert_equal "Message Received", @pipe.gets.text
21
+ assert_equal "Second Message", @pipe.gets.text
22
+ Process.wait(child) #ensure it quits, so there is nothing to write to
23
+ assert_raise IOError do
24
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "anyone there?")
25
+ end
26
+ end
27
+ should "not allow writing if unidentified" do
28
+ assert_raise IOError do
29
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
30
+ end
31
+ end
32
+ should "not allow reading if unidentified" do
33
+ assert_raise IOError do
34
+ @pipe.gets
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,153 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class RunnerTest < Test::Unit::TestCase
4
+ context "with a file to test and a destination to verify" do
5
+ setup do
6
+ sleep(0.2)
7
+ FileUtils.rm_f(target_file)
8
+ FileUtils.rm_f(alternate_target_file)
9
+ end
10
+
11
+ teardown do
12
+ FileUtils.rm_f(target_file)
13
+ FileUtils.rm_f(alternate_target_file)
14
+ end
15
+
16
+
17
+ should "run a test in the foreground" do
18
+ # flip it around to the parent is in the fork, this gives
19
+ # us more direct control over the runner and proper test
20
+ # coverage output
21
+ pipe = Hydra::Pipe.new
22
+ parent = Process.fork do
23
+ request_a_file_and_verify_completion(pipe, test_file)
24
+ end
25
+ run_the_runner(pipe)
26
+ Process.wait(parent)
27
+ end
28
+
29
+ # this flips the above test, so that the main process runs a bit of the parent
30
+ # code, but only with minimal assertion
31
+ should "run a test in the background" do
32
+ pipe = Hydra::Pipe.new
33
+ child = Process.fork do
34
+ run_the_runner(pipe)
35
+ end
36
+ request_a_file_and_verify_completion(pipe, test_file)
37
+ Process.wait(child)
38
+ end
39
+
40
+ should "run a js lint file and find errors" do
41
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
42
+ results = runner.run_file(javascript_file)
43
+ assert results =~ /Missing semicolon/, results
44
+ end
45
+
46
+ should "run a json data file and find errors" do
47
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
48
+ results = runner.run_file(json_file)
49
+ assert results =~ /trailing comma/, results
50
+ end
51
+
52
+ should "run two rspec tests" do
53
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
54
+ runner.run_file(rspec_file)
55
+ assert File.exists?(target_file)
56
+ assert_equal "HYDRA", File.read(target_file)
57
+
58
+ FileUtils.rm_f(target_file)
59
+
60
+ runner.run_file(alternate_rspec_file)
61
+ assert File.exists?(alternate_target_file)
62
+ assert_equal "HYDRA", File.read(alternate_target_file)
63
+ assert !File.exists?(target_file), "Tests are double running!"
64
+ end
65
+
66
+ should "run rspec tests with pending examples" do
67
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
68
+ assert File.exists?(rspec_file_with_pending)
69
+
70
+ runner.run_file(rspec_file_with_pending)
71
+
72
+ assert File.exists?(target_file)
73
+ assert_equal "HYDRA", File.read(target_file)
74
+
75
+ FileUtils.rm_f(target_file)
76
+ end
77
+
78
+ should "run two cucumber tests" do
79
+ # because of all the crap cucumber pulls in
80
+ # we run this in a fork to not contaminate
81
+ # the main test environment
82
+ pid = Process.fork do
83
+ # need to get into the fixtures directory so cucumber doesn't load up the whole project
84
+ Dir.chdir(File.join(File.dirname(__FILE__), 'fixtures'))
85
+
86
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
87
+ runner.run_file(cucumber_feature_file)
88
+ assert File.exists?(target_file)
89
+ assert_equal "HYDRA", File.read(target_file)
90
+
91
+ FileUtils.rm_f(target_file)
92
+
93
+ runner.run_file(alternate_cucumber_feature_file)
94
+ assert File.exists?(alternate_target_file)
95
+ assert_equal "HYDRA", File.read(alternate_target_file)
96
+ assert !File.exists?(target_file)
97
+ end
98
+ Process.wait pid
99
+ end
100
+
101
+ should "be able to run a runner over ssh" do
102
+ ssh = Hydra::SSH.new(
103
+ 'localhost',
104
+ File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
105
+ "ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Runner.new(:io => Hydra::Stdio.new, :verbose => true);\""
106
+ )
107
+ assert ssh.gets.is_a?(Hydra::Messages::Runner::RequestFile)
108
+ ssh.write(Hydra::Messages::Worker::RunFile.new(:file => test_file))
109
+
110
+ # grab its response. This makes us wait for it to finish
111
+ echo = ssh.gets # get the ssh echo
112
+ response = ssh.gets
113
+
114
+ assert_equal Hydra::Messages::Runner::Results, response.class
115
+
116
+ # tell it to shut down
117
+ ssh.write(Hydra::Messages::Worker::Shutdown.new)
118
+
119
+ ssh.close
120
+
121
+ # ensure it ran
122
+ assert File.exists?(target_file)
123
+ assert_equal "HYDRA", File.read(target_file)
124
+ end
125
+ end
126
+
127
+ module RunnerTestHelper
128
+ def request_a_file_and_verify_completion(pipe, file)
129
+ pipe.identify_as_parent
130
+
131
+ # make sure it asks for a file, then give it one
132
+ assert pipe.gets.is_a?(Hydra::Messages::Runner::RequestFile)
133
+ pipe.write(Hydra::Messages::Worker::RunFile.new(:file => file))
134
+
135
+ # grab its response. This makes us wait for it to finish
136
+ response = pipe.gets
137
+
138
+ # tell it to shut down
139
+ pipe.write(Hydra::Messages::Worker::Shutdown.new)
140
+
141
+ # ensure it ran
142
+ assert File.exists?(target_file)
143
+ assert_equal "HYDRA", File.read(target_file)
144
+ end
145
+
146
+ def run_the_runner(pipe)
147
+ pipe.identify_as_child
148
+ Hydra::Runner.new(:io => pipe)
149
+ end
150
+ end
151
+ include RunnerTestHelper
152
+ end
153
+
@@ -0,0 +1,14 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class SSHTest < Test::Unit::TestCase
4
+ should "be able to execute a command over ssh" do
5
+ ssh = Hydra::SSH.new(
6
+ 'localhost', # connect to this machine
7
+ File.expand_path(File.join(File.dirname(__FILE__))), # move to the test directory
8
+ "ruby fixtures/hello_world.rb"
9
+ )
10
+ response = ssh.gets
11
+ assert_equal "Hello World", response.text
12
+ ssh.close
13
+ end
14
+ end
@@ -0,0 +1,113 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class SyncTest < 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 "synchronize a test file over ssh with rsync" do
16
+ local = File.join(Dir.tmpdir, 'hydra', 'local')
17
+ remote = File.join(Dir.tmpdir, 'hydra', 'remote')
18
+ sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
19
+ [local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
20
+
21
+ # setup the folders:
22
+ # local:
23
+ # - test_a
24
+ # - test_c
25
+ # remote:
26
+ # - test_b
27
+ #
28
+ # add test_c to exludes
29
+ FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
30
+ FileUtils.cp(sync_test, File.join(local, 'test_c.rb'))
31
+ FileUtils.cp(sync_test, File.join(remote, 'test_b.rb'))
32
+
33
+ # ensure a is not on remote
34
+ assert !File.exists?(File.join(remote, 'test_a.rb')), "A should not be on remote"
35
+ # ensure c is not on remote
36
+ assert !File.exists?(File.join(remote, 'test_c.rb')), "C should not be on remote"
37
+ # ensure b is on remote
38
+ assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
39
+
40
+ Hydra::Sync.new(
41
+ {
42
+ :type => :ssh,
43
+ :connect => 'localhost',
44
+ :directory => remote,
45
+ :runners => 1
46
+ },
47
+ {
48
+ :directory => local,
49
+ :exclude => ['test_c.rb']
50
+ }
51
+ )
52
+ # ensure a is copied
53
+ assert File.exists?(File.join(remote, 'test_a.rb')), "A was not copied"
54
+ # ensure c is not copied
55
+ assert !File.exists?(File.join(remote, 'test_c.rb')), "C was copied, should be excluded"
56
+ # ensure b is deleted
57
+ assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
58
+ end
59
+
60
+ should "synchronize a test file over ssh with rsync to multiple workers" do
61
+ local = File.join(Dir.tmpdir, 'hydra', 'local')
62
+ remote_a = File.join(Dir.tmpdir, 'hydra', 'remote_a')
63
+ remote_b = File.join(Dir.tmpdir, 'hydra', 'remote_b')
64
+ sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
65
+ [local, remote_a, remote_b].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
66
+
67
+ # setup the folders:
68
+ # local:
69
+ # - test_a
70
+ # remote_a:
71
+ # - test_b
72
+ # remote_b:
73
+ # - test_c
74
+ #
75
+ # add test_c to exludes
76
+ FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
77
+ FileUtils.cp(sync_test, File.join(remote_a, 'test_b.rb'))
78
+ FileUtils.cp(sync_test, File.join(remote_b, 'test_c.rb'))
79
+
80
+ # ensure a is not on remotes
81
+ assert !File.exists?(File.join(remote_a, 'test_a.rb')), "A should not be on remote_a"
82
+ assert !File.exists?(File.join(remote_b, 'test_a.rb')), "A should not be on remote_b"
83
+ # ensure b is on remote_a
84
+ assert File.exists?(File.join(remote_a, 'test_b.rb')), "B should be on remote_a"
85
+ # ensure c is on remote_b
86
+ assert File.exists?(File.join(remote_b, 'test_c.rb')), "C should be on remote_b"
87
+
88
+ Hydra::Sync.sync_many(
89
+ :workers => [{
90
+ :type => :ssh,
91
+ :connect => 'localhost',
92
+ :directory => remote_a,
93
+ :runners => 1
94
+ },
95
+ {
96
+ :type => :ssh,
97
+ :connect => 'localhost',
98
+ :directory => remote_b,
99
+ :runners => 1
100
+ }],
101
+ :sync => {
102
+ :directory => local
103
+ }
104
+ )
105
+ # ensure a is copied to both remotes
106
+ assert File.exists?(File.join(remote_a, 'test_a.rb')), "A was not copied to remote_a"
107
+ assert File.exists?(File.join(remote_b, 'test_a.rb')), "A was not copied to remote_b"
108
+ # ensure b and c are deleted from remotes
109
+ assert !File.exists?(File.join(remote_a, 'test_b.rb')), "B was not deleted from remote_a"
110
+ assert !File.exists?(File.join(remote_b, 'test_c.rb')), "C was not deleted from remote_b"
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,68 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'tmpdir'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'hydra'
9
+
10
+ # Since Hydra turns off testing, we have to turn it back on
11
+ Test::Unit.run = false
12
+
13
+ class Test::Unit::TestCase
14
+ def target_file
15
+ File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt'))
16
+ end
17
+
18
+ def alternate_target_file
19
+ File.expand_path(File.join(Dir.tmpdir, 'alternate_hydra_test.txt'))
20
+ end
21
+
22
+ def test_file
23
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file.rb'))
24
+ end
25
+
26
+ def rspec_file
27
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_spec.rb'))
28
+ end
29
+
30
+ def alternate_rspec_file
31
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_alternate_spec.rb'))
32
+ end
33
+
34
+ def rspec_file_with_pending
35
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_with_pending_spec.rb'))
36
+ end
37
+
38
+ def cucumber_feature_file
39
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_file.feature'))
40
+ end
41
+
42
+ def alternate_cucumber_feature_file
43
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_alternate_file.feature'))
44
+ end
45
+
46
+ def javascript_file
47
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'js_file.js'))
48
+ end
49
+
50
+ def json_file
51
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'json_data.json'))
52
+ end
53
+ end
54
+
55
+ module Hydra #:nodoc:
56
+ module Messages #:nodoc:
57
+ class TestMessage < Hydra::Message
58
+ attr_accessor :text
59
+ def initialize(opts = {})
60
+ @text = opts.fetch(:text){ "test" }
61
+ end
62
+ def serialize
63
+ super(:text => @text)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
@@ -0,0 +1,60 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class WorkerTest < Test::Unit::TestCase
4
+ context "with a file to test and a destination to verify" do
5
+ setup do
6
+ FileUtils.rm_f(target_file)
7
+ end
8
+
9
+ teardown do
10
+ FileUtils.rm_f(target_file)
11
+ end
12
+
13
+ # run the worker in the foreground and the requests in the background
14
+ should "run a test in the foreground" do
15
+ num_runners = 4
16
+ pipe = Hydra::Pipe.new
17
+ child = Process.fork do
18
+ request_a_file_and_verify_completion(pipe, num_runners)
19
+ end
20
+ run_the_worker(pipe, num_runners)
21
+ Process.wait(child)
22
+ end
23
+
24
+ # inverse of the above test to run the worker in the background
25
+ should "run a test in the background" do
26
+ num_runners = 4
27
+ pipe = Hydra::Pipe.new
28
+ child = Process.fork do
29
+ run_the_worker(pipe, num_runners)
30
+ end
31
+ request_a_file_and_verify_completion(pipe, num_runners)
32
+ Process.wait(child)
33
+ end
34
+ end
35
+
36
+ module WorkerTestHelper
37
+ def run_the_worker(pipe, num_runners)
38
+ pipe.identify_as_child
39
+ Hydra::Worker.new({:io => pipe, :runners => num_runners})
40
+ end
41
+
42
+ def request_a_file_and_verify_completion(pipe, num_runners)
43
+ pipe.identify_as_parent
44
+ pipe.gets # grab the WorkerBegin
45
+ num_runners.times do
46
+ response = pipe.gets # grab the RequestFile
47
+ assert response.is_a?(Hydra::Messages::Worker::RequestFile), "Expected RequestFile but got #{response.class.to_s}"
48
+ end
49
+ pipe.write(Hydra::Messages::Master::RunFile.new(:file => test_file))
50
+
51
+ assert pipe.gets.is_a?(Hydra::Messages::Worker::Results)
52
+
53
+ pipe.write(Hydra::Messages::Master::Shutdown.new)
54
+
55
+ assert File.exists?(target_file)
56
+ assert_equal "HYDRA", File.read(target_file)
57
+ end
58
+ end
59
+ include WorkerTestHelper
60
+ end