hydra 0.24.0 → 6.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +17 -0
  2. data/CONTRIBUTING.md +75 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +14 -0
  5. data/README.md +36 -0
  6. data/RELEASE-POLICY.md +10 -0
  7. data/Rakefile +1 -56
  8. data/hydra.gemspec +29 -124
  9. data/lib/hydra.rb +5 -16
  10. data/lib/hydra/version.rb +3 -0
  11. metadata +180 -108
  12. data/.document +0 -5
  13. data/LICENSE +0 -20
  14. data/README.rdoc +0 -43
  15. data/TODO +0 -18
  16. data/VERSION +0 -1
  17. data/caliper.yml +0 -6
  18. data/hydra-icon-64x64.png +0 -0
  19. data/hydra_gray.png +0 -0
  20. data/lib/hydra/cucumber/formatter.rb +0 -29
  21. data/lib/hydra/cucumber/partial_html.rb +0 -24
  22. data/lib/hydra/hash.rb +0 -16
  23. data/lib/hydra/js/lint.js +0 -5150
  24. data/lib/hydra/listener/abstract.rb +0 -39
  25. data/lib/hydra/listener/cucumber.css +0 -279
  26. data/lib/hydra/listener/cucumber_html_report.rb +0 -148
  27. data/lib/hydra/listener/jquery-min.js +0 -154
  28. data/lib/hydra/listener/minimal_output.rb +0 -24
  29. data/lib/hydra/listener/notifier.rb +0 -17
  30. data/lib/hydra/listener/progress_bar.rb +0 -48
  31. data/lib/hydra/listener/report_generator.rb +0 -33
  32. data/lib/hydra/master.rb +0 -248
  33. data/lib/hydra/message.rb +0 -47
  34. data/lib/hydra/message/master_messages.rb +0 -19
  35. data/lib/hydra/message/runner_messages.rb +0 -46
  36. data/lib/hydra/message/worker_messages.rb +0 -52
  37. data/lib/hydra/messaging_io.rb +0 -49
  38. data/lib/hydra/pipe.rb +0 -61
  39. data/lib/hydra/runner.rb +0 -312
  40. data/lib/hydra/runner_listener/abstract.rb +0 -23
  41. data/lib/hydra/safe_fork.rb +0 -31
  42. data/lib/hydra/spec/autorun_override.rb +0 -3
  43. data/lib/hydra/spec/hydra_formatter.rb +0 -26
  44. data/lib/hydra/ssh.rb +0 -41
  45. data/lib/hydra/stdio.rb +0 -16
  46. data/lib/hydra/sync.rb +0 -99
  47. data/lib/hydra/tasks.rb +0 -375
  48. data/lib/hydra/tmpdir.rb +0 -11
  49. data/lib/hydra/trace.rb +0 -24
  50. data/lib/hydra/worker.rb +0 -170
  51. data/test/fixtures/assert_true.rb +0 -7
  52. data/test/fixtures/config.yml +0 -4
  53. data/test/fixtures/conflicting.rb +0 -10
  54. data/test/fixtures/features/step_definitions.rb +0 -21
  55. data/test/fixtures/features/write_alternate_file.feature +0 -7
  56. data/test/fixtures/features/write_file.feature +0 -7
  57. data/test/fixtures/hello_world.rb +0 -3
  58. data/test/fixtures/hydra_worker_init.rb +0 -2
  59. data/test/fixtures/js_file.js +0 -4
  60. data/test/fixtures/json_data.json +0 -4
  61. data/test/fixtures/many_outputs_to_console.rb +0 -9
  62. data/test/fixtures/master_listeners.rb +0 -10
  63. data/test/fixtures/runner_listeners.rb +0 -23
  64. data/test/fixtures/slow.rb +0 -9
  65. data/test/fixtures/sync_test.rb +0 -8
  66. data/test/fixtures/task_test_config.yml +0 -6
  67. data/test/fixtures/write_file.rb +0 -10
  68. data/test/fixtures/write_file_alternate_spec.rb +0 -10
  69. data/test/fixtures/write_file_spec.rb +0 -9
  70. data/test/fixtures/write_file_with_pending_spec.rb +0 -11
  71. data/test/master_test.rb +0 -383
  72. data/test/message_test.rb +0 -31
  73. data/test/pipe_test.rb +0 -38
  74. data/test/runner_test.rb +0 -196
  75. data/test/ssh_test.rb +0 -25
  76. data/test/sync_test.rb +0 -113
  77. data/test/task_test.rb +0 -21
  78. data/test/test_helper.rb +0 -107
  79. data/test/worker_test.rb +0 -60
@@ -1,47 +0,0 @@
1
- module Hydra #:nodoc:
2
- # Base message object. Used to pass messages with parameters around
3
- # via IO objects.
4
- # class MyMessage < Hydra::Message
5
- # attr_accessor :my_var
6
- # def serialize
7
- # super(:my_var => @my_var)
8
- # end
9
- # end
10
- # m = MyMessage.new(:my_var => 'my value')
11
- # m.my_var
12
- # => "my value"
13
- # m.serialize
14
- # => "{:class=>TestMessage::MyMessage, :my_var=>\"my value\"}"
15
- # Hydra::Message.build(eval(@m.serialize)).my_var
16
- # => "my value"
17
- class Message
18
- # Create a new message. Opts is a hash where the keys
19
- # are attributes of the message and the values are
20
- # set to the attribute.
21
- def initialize(opts = {})
22
- opts.delete :class
23
- opts.each do |variable,value|
24
- self.send("#{variable}=",value)
25
- end
26
- end
27
-
28
- # Build a message from a hash. The hash must contain
29
- # the :class symbol, which is the class of the message
30
- # that it will build to.
31
- def self.build(hash)
32
- hash.delete(:class).new(hash)
33
- end
34
-
35
- # Serialize the message for output on an IO channel.
36
- # This is really just a string representation of a hash
37
- # with no newlines. It adds in the class automatically
38
- def serialize(opts = {})
39
- opts.merge({:class => self.class}).inspect
40
- end
41
- end
42
- end
43
-
44
- require 'hydra/message/runner_messages'
45
- require 'hydra/message/worker_messages'
46
- require 'hydra/message/master_messages'
47
-
@@ -1,19 +0,0 @@
1
- module Hydra #:nodoc:
2
- module Messages #:nodoc:
3
- module Master #:nodoc:
4
- # Message telling a worker to delegate a file to a runner
5
- class RunFile < Hydra::Messages::Worker::RunFile
6
- def handle(worker) #:nodoc:
7
- worker.delegate_file(self)
8
- end
9
- end
10
-
11
- # Message telling the worker to shut down.
12
- class Shutdown < Hydra::Messages::Worker::Shutdown
13
- def handle(worker) #:nodoc:
14
- worker.shutdown
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,46 +0,0 @@
1
- module Hydra #:nodoc:
2
- module Messages #:nodoc:
3
- module Runner #:nodoc:
4
- # Message indicating that a Runner needs a file to run
5
- class RequestFile < Hydra::Message
6
- def handle(worker, runner) #:nodoc:
7
- worker.request_file(self, runner)
8
- end
9
- end
10
-
11
- # Message for the Runner to respond with its results
12
- class Results < Hydra::Message
13
- # The output from running the test
14
- attr_accessor :output
15
- # The file that was run
16
- attr_accessor :file
17
- def serialize #:nodoc:
18
- super(:output => @output, :file => @file)
19
- end
20
- def handle(worker, runner) #:nodoc:
21
- worker.relay_results(self, runner)
22
- end
23
- end
24
-
25
- # Message a runner sends to a worker to verify the connection
26
- class Ping < Hydra::Message
27
- def handle(worker, runner) #:nodoc:
28
- # We don't do anything to handle a ping. It's just to test
29
- # the connectivity of the IO
30
- end
31
- end
32
-
33
- # The runner forks to run rspec messages
34
- # so that specs don't get rerun. It uses
35
- # this message to report the results. See
36
- # Runner::run_rspec_file.
37
- class RSpecResult < Hydra::Message
38
- # the output of the spec
39
- attr_accessor :output
40
- def serialize #:nodoc:
41
- super(:output => @output)
42
- end
43
- end
44
- end
45
- end
46
- end
@@ -1,52 +0,0 @@
1
- module Hydra #:nodoc:
2
- module Messages #:nodoc:
3
- module Worker #:nodoc:
4
- # Message indicating that a worker needs a file to delegate to a runner
5
- class RequestFile < Hydra::Message
6
- def handle(master, worker) #:nodoc:
7
- master.send_file(worker)
8
- end
9
- end
10
-
11
- class WorkerBegin < Hydra::Message
12
- def handle(master, worker)
13
- master.worker_begin(worker)
14
- end
15
- end
16
-
17
- # Message telling the Runner to run a file
18
- class RunFile < Hydra::Message
19
- # The file that should be run
20
- attr_accessor :file
21
- def serialize #:nodoc:
22
- super(:file => @file)
23
- end
24
- def handle(runner) #:nodoc:
25
- runner.run_file(@file)
26
- end
27
- end
28
-
29
- # Message to tell the Runner to shut down
30
- class Shutdown < Hydra::Message
31
- def handle(runner) #:nodoc:
32
- runner.stop
33
- end
34
- end
35
-
36
- # Message relaying the results of a worker up to the master
37
- class Results < Hydra::Messages::Runner::Results
38
- def handle(master, worker) #:nodoc:
39
- master.process_results(worker, self)
40
- end
41
- end
42
-
43
- # Message a worker sends to a master to verify the connection
44
- class Ping < Hydra::Message
45
- def handle(master, worker) #:nodoc:
46
- # We don't do anything to handle a ping. It's just to test
47
- # the connectivity of the IO
48
- end
49
- end
50
- end
51
- end
52
- end
@@ -1,49 +0,0 @@
1
- module Hydra #:nodoc:
2
- # Module that implemets methods that auto-serialize and deserialize messaging
3
- # objects.
4
- module MessagingIO
5
- # Read a Message from the input IO object. Automatically build
6
- # a message from the response and return it.
7
- #
8
- # IO.gets
9
- # => Hydra::Message # or subclass
10
- def gets
11
- while true
12
- begin
13
- raise IOError unless @reader
14
- message = @reader.gets
15
- return nil unless message
16
- return Message.build(eval(message.chomp))
17
- rescue SyntaxError, NameError
18
- # uncomment to help catch remote errors by seeing all traffic
19
- #$stderr.write "Not a message: [#{message.inspect}]\n"
20
- end
21
- end
22
- end
23
-
24
- # Write a Message to the output IO object. It will automatically
25
- # serialize a Message object.
26
- # IO.write Hydra::Message.new
27
- def write(message)
28
- raise IOError unless @writer
29
- raise UnprocessableMessage unless message.is_a?(Hydra::Message)
30
- @writer.write(message.serialize+"\n")
31
- rescue Errno::EPIPE
32
- raise IOError
33
- end
34
-
35
- # Closes the IO object.
36
- def close
37
- @reader.close if @reader
38
- @writer.close if @writer
39
- end
40
-
41
- # IO will return this error if it cannot process a message.
42
- # For example, if you tried to write a string, it would fail,
43
- # because the string is not a message.
44
- class UnprocessableMessage < RuntimeError
45
- # Custom error message
46
- attr_accessor :message
47
- end
48
- end
49
- end
@@ -1,61 +0,0 @@
1
- require 'hydra/messaging_io'
2
- module Hydra #:nodoc:
3
- # Read and write between two processes via pipes. For example:
4
- # @pipe = Hydra::Pipe.new
5
- # @child = Process.fork do
6
- # @pipe.identify_as_child
7
- # puts "A message from my parent:\n#{@pipe.gets.text}"
8
- # @pipe.close
9
- # end
10
- # @pipe.identify_as_parent
11
- # @pipe.write Hydra::Messages::TestMessage.new(:text => "Hello!")
12
- # @pipe.close
13
- #
14
- # Note that the TestMessage class is only available in tests, and
15
- # not in Hydra by default.
16
- #
17
- #
18
- # When the process forks, the pipe is copied. When a pipe is
19
- # identified as a parent or child, it is choosing which ends
20
- # of the pipe to use.
21
- #
22
- # A pipe is actually two pipes:
23
- #
24
- # Parent == Pipe 1 ==> Child
25
- # Parent <== Pipe 2 == Child
26
- #
27
- # It's like if you had two cardboard tubes and you were using
28
- # them to drop balls with messages in them between processes.
29
- # One tube is for sending from parent to child, and the other
30
- # tube is for sending from child to parent.
31
- class Pipe
32
- include Hydra::MessagingIO
33
- # Creates a new uninitialized pipe pair.
34
- def initialize
35
- @child_read, @parent_write = IO.pipe
36
- @parent_read, @child_write = IO.pipe
37
- end
38
-
39
- # Identify this side of the pipe as the child.
40
- def identify_as_child
41
- @parent_write.close
42
- @parent_read.close
43
- @reader = @child_read
44
- @writer = @child_write
45
- end
46
-
47
- # Identify this side of the pipe as the parent
48
- def identify_as_parent
49
- @child_write.close
50
- @child_read.close
51
- @reader = @parent_read
52
- @writer = @parent_write
53
- end
54
-
55
- # Output pipe nicely
56
- def inspect
57
- "#<#{self.class} @reader=#{@reader.to_s}, @writer=#{@writer.to_s}>"
58
- end
59
-
60
- end
61
- end
@@ -1,312 +0,0 @@
1
- require 'test/unit'
2
- require 'test/unit/testresult'
3
- Test::Unit.run = true
4
-
5
- module Hydra #:nodoc:
6
- # Hydra class responsible for running test files.
7
- #
8
- # The Runner is never run directly by a user. Runners are created by a
9
- # Worker to run test files.
10
- #
11
- # The general convention is to have one Runner for each logical processor
12
- # of a machine.
13
- class Runner
14
- include Hydra::Messages::Runner
15
- traceable('RUNNER')
16
-
17
- DEFAULT_LOG_FILE = 'hydra-runner.log'
18
-
19
- # Boot up a runner. It takes an IO object (generally a pipe from its
20
- # parent) to send it messages on which files to execute.
21
- def initialize(opts = {})
22
- redirect_output( opts.fetch( :runner_log_file ) { DEFAULT_LOG_FILE } )
23
- reg_trap_sighup
24
-
25
- @io = opts.fetch(:io) { raise "No IO Object" }
26
- @verbose = opts.fetch(:verbose) { false }
27
- @event_listeners = Array( opts.fetch( :runner_listeners ) { nil } )
28
- @options = opts.fetch(:options) { "" }
29
- @directory = get_directory
30
-
31
- $stdout.sync = true
32
- runner_begin
33
-
34
- trace 'Booted. Sending Request for file'
35
- @io.write RequestFile.new
36
- begin
37
- process_messages
38
- rescue => ex
39
- trace ex.to_s
40
- raise ex
41
- end
42
- end
43
-
44
- def reg_trap_sighup
45
- for sign in [:SIGHUP, :INT]
46
- trap sign do
47
- stop
48
- end
49
- end
50
- @runner_began = true
51
- end
52
-
53
- def runner_begin
54
- trace "Firing runner_begin event"
55
- @event_listeners.each {|l| l.runner_begin( self ) }
56
- end
57
-
58
- # Run a test file and report the results
59
- def run_file(file)
60
- trace "Running file: #{file}"
61
-
62
- output = ""
63
- if file =~ /_spec.rb$/i
64
- output = run_rspec_file(file)
65
- elsif file =~ /.feature$/i
66
- output = run_cucumber_file(file)
67
- elsif file =~ /.js$/i or file =~ /.json$/i
68
- output = run_javascript_file(file)
69
- else
70
- output = run_test_unit_file(file)
71
- end
72
-
73
- output = "." if output == ""
74
-
75
- @io.write Results.new(:output => output, :file => file)
76
- return output
77
- end
78
-
79
- # Stop running
80
- def stop
81
- runner_end if @runner_began
82
- @runner_began = @running = false
83
- end
84
-
85
- def runner_end
86
- trace "Ending runner #{self.inspect}"
87
- @event_listeners.each {|l| l.runner_end( self ) }
88
- end
89
-
90
- def format_exception(ex)
91
- "#{ex.class.name}: #{ex.message}\n #{ex.backtrace.join("\n ")}"
92
- end
93
-
94
- private
95
-
96
- # The runner will continually read messages and handle them.
97
- def process_messages
98
- trace "Processing Messages"
99
- @running = true
100
- while @running
101
- begin
102
- message = @io.gets
103
- if message and !message.class.to_s.index("Worker").nil?
104
- trace "Received message from worker"
105
- trace "\t#{message.inspect}"
106
- message.handle(self)
107
- else
108
- @io.write Ping.new
109
- end
110
- rescue IOError => ex
111
- trace "Runner lost Worker"
112
- stop
113
- end
114
- end
115
- end
116
-
117
- def format_ex_in_file(file, ex)
118
- "Error in #{file}:\n #{format_exception(ex)}"
119
- end
120
-
121
- # Run all the Test::Unit Suites in a ruby file
122
- def run_test_unit_file(file)
123
- begin
124
- require @directory + file
125
- rescue LoadError => ex
126
- trace "#{file} does not exist [#{ex.to_s}]"
127
- return ex.to_s
128
- rescue Exception => ex
129
- trace "Error requiring #{file} [#{ex.to_s}]"
130
- return format_ex_in_file(file, ex)
131
- end
132
- output = []
133
- @result = Test::Unit::TestResult.new
134
- @result.add_listener(Test::Unit::TestResult::FAULT) do |value|
135
- output << value
136
- end
137
-
138
- klasses = Runner.find_classes_in_file(file)
139
- begin
140
- klasses.each{|klass| klass.suite.run(@result){|status, name| ;}}
141
- rescue => ex
142
- output << format_ex_in_file(file, ex)
143
- end
144
-
145
- return output.join("\n")
146
- end
147
-
148
- # run all the Specs in an RSpec file (NOT IMPLEMENTED)
149
- def run_rspec_file(file)
150
- # pull in rspec
151
- begin
152
- require 'rspec'
153
- require 'hydra/spec/hydra_formatter'
154
- # Ensure we override rspec's at_exit
155
- RSpec::Core::Runner.disable_autorun!
156
- rescue LoadError => ex
157
- return ex.to_s
158
- end
159
- hydra_output = StringIO.new
160
-
161
- config = [
162
- '-f', 'RSpec::Core::Formatters::HydraFormatter',
163
- file
164
- ]
165
-
166
- RSpec.instance_variable_set(:@world, nil)
167
- RSpec::Core::Runner.run(config, hydra_output, hydra_output)
168
-
169
- hydra_output.rewind
170
- output = hydra_output.read.chomp
171
- output = "" if output.gsub("\n","") =~ /^\.*$/
172
-
173
- return output
174
- end
175
-
176
- # run all the scenarios in a cucumber feature file
177
- def run_cucumber_file(file)
178
- hydra_response = StringIO.new
179
-
180
- options = @options if @options.is_a?(Array)
181
- options = @options.split(' ') if @options.is_a?(String)
182
-
183
- fork_id = fork do
184
- files = [file]
185
- dev_null = StringIO.new
186
-
187
- args = [file, options].flatten.compact
188
- hydra_response.puts args.inspect
189
-
190
- results_directory = "#{Dir.pwd}/results/features"
191
- FileUtils.mkdir_p results_directory
192
-
193
- require 'cucumber/cli/main'
194
- require 'hydra/cucumber/formatter'
195
- require 'hydra/cucumber/partial_html'
196
-
197
- Cucumber.logger.level = Logger::INFO
198
-
199
- cuke = Cucumber::Cli::Main.new(args, dev_null, dev_null)
200
- cuke.configuration.formats << ['Cucumber::Formatter::Hydra', hydra_response]
201
-
202
- html_output = cuke.configuration.formats.select{|format| format[0] == 'html'}
203
- if html_output
204
- cuke.configuration.formats.delete(html_output)
205
- cuke.configuration.formats << ['Hydra::Formatter::PartialHtml', "#{results_directory}/#{file.split('/').last}.html"]
206
- end
207
-
208
- cuke_runtime = Cucumber::Runtime.new(cuke.configuration)
209
- cuke_runtime.run!
210
- exit 1 if cuke_runtime.results.failure?
211
- end
212
- Process.wait fork_id
213
-
214
- hydra_response.puts "." if not $?.exitstatus == 0
215
- hydra_response.rewind
216
-
217
- hydra_response.read
218
- end
219
-
220
- def run_javascript_file(file)
221
- errors = []
222
- require 'v8'
223
- V8::Context.new do |context|
224
- context.load(File.expand_path(File.join(File.dirname(__FILE__), 'js', 'lint.js')))
225
- context['input'] = lambda{
226
- File.read(file)
227
- }
228
- context['reportErrors'] = lambda{|js_errors|
229
- js_errors.each do |e|
230
- e = V8::To.rb(e)
231
- errors << "\n\e[1;31mJSLINT: #{file}\e[0m"
232
- errors << " Error at line #{e['line'].to_i + 1} " +
233
- "character #{e['character'].to_i + 1}: \e[1;33m#{e['reason']}\e[0m"
234
- errors << "#{e['evidence']}"
235
- end
236
- }
237
- context.eval %{
238
- JSLINT(input(), {
239
- sub: true,
240
- onevar: true,
241
- eqeqeq: true,
242
- plusplus: true,
243
- bitwise: true,
244
- regexp: true,
245
- newcap: true,
246
- immed: true,
247
- strict: true,
248
- rhino: true
249
- });
250
- reportErrors(JSLINT.errors);
251
- }
252
- end
253
-
254
- if errors.empty?
255
- return '.'
256
- else
257
- return errors.join("\n")
258
- end
259
- end
260
-
261
- # find all the test unit classes in a given file, so we can run their suites
262
- def self.find_classes_in_file(f)
263
- code = ""
264
- File.open(f) {|buffer| code = buffer.read}
265
- matches = code.scan(/class\s+([\S]+)/)
266
- klasses = matches.collect do |c|
267
- begin
268
- if c.first.respond_to? :constantize
269
- c.first.constantize
270
- else
271
- eval(c.first)
272
- end
273
- rescue NameError
274
- # means we could not load [c.first], but thats ok, its just not
275
- # one of the classes we want to test
276
- nil
277
- rescue SyntaxError
278
- # see above
279
- nil
280
- end
281
- end
282
- return klasses.select{|k| k.respond_to? 'suite'}
283
- end
284
-
285
- # Yanked a method from Cucumber
286
- def tag_excess(features, limits)
287
- limits.map do |tag_name, tag_limit|
288
- tag_locations = features.tag_locations(tag_name)
289
- if tag_limit && (tag_locations.length > tag_limit)
290
- [tag_name, tag_limit, tag_locations]
291
- else
292
- nil
293
- end
294
- end.compact
295
- end
296
-
297
- def redirect_output file_name
298
- begin
299
- $stderr = $stdout = File.open(file_name, 'a')
300
- rescue
301
- # it should always redirect output in order to handle unexpected interruption
302
- # successfully
303
- $stderr = $stdout = File.open(DEFAULT_LOG_FILE, 'a')
304
- end
305
- end
306
-
307
- def get_directory
308
- RUBY_VERSION < "1.9" ? "" : Dir.pwd + "/"
309
- end
310
- end
311
- end
312
-