hydra 0.12.0 → 0.13.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/TODO CHANGED
@@ -1,8 +1,18 @@
1
1
  = Hydra TODO
2
2
 
3
- Fix test runner output. Local runners are dumping test unit summaries
4
- and it looks bad.
3
+ * hydra:sync task that runs the SSH syncing for remote workers
4
+ * ensure same version is running remotely (gem directive)
5
+ * on a crash, bubble up error messages
6
+ * send workers a "boot" message with all the files that will be tested so that it
7
+ can boot the environment before forking runners
8
+ * named configurations (i.e. 'local', 'remote', 'myconfig') so users can swap configs with an
9
+ environment variable or with a hydra testtask option
5
10
 
6
- Add in a time report
11
+ == Reporting
7
12
 
13
+ Refactor reporting into an event listening system. Add in a default listener that messages:
14
+
15
+ * Files at start
16
+ * Progress status "50% (10/20 files)"
17
+ * Time report at the end
8
18
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.12.0
1
+ 0.13.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{hydra}
8
- s.version = "0.12.0"
8
+ s.version = "0.13.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-18}
12
+ s.date = %q{2010-03-25}
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 = [
@@ -30,6 +30,9 @@ Gem::Specification.new do |s|
30
30
  "hydra_gray.png",
31
31
  "lib/hydra.rb",
32
32
  "lib/hydra/hash.rb",
33
+ "lib/hydra/listener/abstract.rb",
34
+ "lib/hydra/listener/minimal_output.rb",
35
+ "lib/hydra/listener/report_generator.rb",
33
36
  "lib/hydra/master.rb",
34
37
  "lib/hydra/message.rb",
35
38
  "lib/hydra/message/master_messages.rb",
@@ -7,4 +7,8 @@ require 'hydra/safe_fork'
7
7
  require 'hydra/runner'
8
8
  require 'hydra/worker'
9
9
  require 'hydra/master'
10
+ require 'hydra/listener/abstract'
11
+ require 'hydra/listener/minimal_output'
12
+ require 'hydra/listener/report_generator'
13
+
10
14
 
@@ -0,0 +1,30 @@
1
+ module Hydra #:nodoc:
2
+ module Listener #:nodoc:
3
+ # Abstract listener that implements all the events
4
+ # but does nothing.
5
+ class Abstract
6
+ # Create a new listener.
7
+ #
8
+ # Output: The IO object for outputting any information.
9
+ # Defaults to STDOUT, but you could pass a file in, or STDERR
10
+ def initialize(output = $stdout)
11
+ @output = output
12
+ end
13
+ # Fired when testing has started
14
+ def testing_begin(files)
15
+ end
16
+
17
+ # Fired when testing finishes
18
+ def testing_end
19
+ end
20
+
21
+ # Fired when a file is started
22
+ def file_begin(file)
23
+ end
24
+
25
+ # Fired when a file is finished
26
+ def file_end(file, output)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ module Hydra #:nodoc:
2
+ module Listener #:nodoc:
3
+ # Minimal output listener. Outputs all the files at the start
4
+ # of testing and outputs a ./F/E per file. As well as
5
+ # full error output, if any.
6
+ class MinimalOutput < Hydra::Listener::Abstract
7
+ # output a starting message
8
+ def testing_begin(files)
9
+ @output.write "Hydra Testing:\n#{files.inspect}\n"
10
+ end
11
+
12
+ # output a finished message
13
+ def testing_end
14
+ @output.write "\nHydra Completed\n"
15
+ end
16
+
17
+ # For each file, just output a . for a successful file, or the
18
+ # Failure/Error output from the tests
19
+ def file_end(file, output)
20
+ @output.write output
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ module Hydra #:nodoc:
2
+ module Listener #:nodoc:
3
+ # Output a textual report at the end of testing
4
+ class ReportGenerator < Hydra::Listener::Abstract
5
+ # Initialize a new report
6
+ def testing_begin(files)
7
+ @report = { }
8
+ end
9
+
10
+ # Log the start time of a file
11
+ def file_begin(file)
12
+ @report[file] ||= { }
13
+ @report[file]['start'] = Time.now.to_f
14
+ end
15
+
16
+ # Log the end time of a file and compute the file's testing
17
+ # duration
18
+ def file_end(file, output)
19
+ @report[file]['end'] = Time.now.to_f
20
+ @report[file]['duration'] = @report[file]['end'] - @report[file]['start']
21
+ end
22
+
23
+ # output the report
24
+ def testing_end
25
+ YAML.dump(@report, @output)
26
+ @output.close
27
+ end
28
+ end
29
+ end
30
+ end
@@ -19,6 +19,13 @@ module Hydra #:nodoc:
19
19
  # * :workers
20
20
  # * An array of hashes. Each hash should be the configuration options
21
21
  # for a worker.
22
+ # * :listeners
23
+ # * An array of Hydra::Listener objects. See Hydra::Listener::MinimalOutput for an
24
+ # example listener
25
+ # * :verbose
26
+ # * Set to true to see lots of Hydra output (for debugging)
27
+ # * :autosort
28
+ # * Set to false to disable automatic sorting by historical run-time per file
22
29
  def initialize(opts = { })
23
30
  opts.stringify_keys!
24
31
  config_file = opts.delete('config') { nil }
@@ -30,13 +37,16 @@ module Hydra #:nodoc:
30
37
  @incomplete_files = @files.dup
31
38
  @workers = []
32
39
  @listeners = []
40
+ @event_listeners = Array(opts.fetch('listeners') { nil } )
33
41
  @verbose = opts.fetch('verbose') { false }
34
- @report = opts.fetch('report') { false }
35
42
  @autosort = opts.fetch('autosort') { true }
36
- sort_files_from_report if @autosort
37
- init_report_file
38
43
  @sync = opts.fetch('sync') { nil }
39
44
 
45
+ if @autosort
46
+ sort_files_from_report
47
+ @event_listeners << Hydra::Listener::ReportGenerator.new(File.new(heuristic_file, 'w'))
48
+ end
49
+
40
50
  # default is one worker that is configured to use a pipe with one runner
41
51
  worker_cfg = opts.fetch('workers') { [ { 'type' => 'local', 'runners' => 1} ] }
42
52
 
@@ -45,6 +55,8 @@ module Hydra #:nodoc:
45
55
  trace " Workers: (#{worker_cfg.inspect})"
46
56
  trace " Verbose: (#{@verbose.inspect})"
47
57
 
58
+ @event_listeners.each{|l| l.testing_begin(@files) }
59
+
48
60
  boot_workers worker_cfg
49
61
  process_messages
50
62
  end
@@ -56,7 +68,7 @@ module Hydra #:nodoc:
56
68
  f = @files.shift
57
69
  if f
58
70
  trace "Sending #{f.inspect}"
59
- report_start_time(f)
71
+ @event_listeners.each{|l| l.file_begin(f) }
60
72
  worker[:io].write(RunFile.new(:file => f))
61
73
  else
62
74
  trace "No more files to send"
@@ -65,11 +77,9 @@ module Hydra #:nodoc:
65
77
 
66
78
  # Process the results coming back from the worker.
67
79
  def process_results(worker, message)
68
- $stdout.write message.output
69
- # only delete one
70
80
  @incomplete_files.delete_at(@incomplete_files.index(message.file))
71
81
  trace "#{@incomplete_files.size} Files Remaining"
72
- report_finish_time(message.file)
82
+ @event_listeners.each{|l| l.file_end(message.file, message.output) }
73
83
  if @incomplete_files.empty?
74
84
  shutdown_all_workers
75
85
  else
@@ -185,60 +195,25 @@ module Hydra #:nodoc:
185
195
  end
186
196
 
187
197
  @listeners.each{|l| l.join}
188
-
189
- generate_report
190
- end
191
-
192
- def init_report_file
193
- FileUtils.rm_f(report_file)
194
- FileUtils.rm_f(report_results_file)
195
- end
196
-
197
- def report_start_time(file)
198
- File.open(report_file, 'a'){|f| f.write "#{file}|start|#{Time.now.to_f}\n" }
199
- end
200
-
201
- def report_finish_time(file)
202
- File.open(report_file, 'a'){|f| f.write "#{file}|finish|#{Time.now.to_f}\n" }
203
- end
204
-
205
- def generate_report
206
- report = {}
207
- lines = nil
208
- File.open(report_file, 'r'){|f| lines = f.read.split("\n")}
209
- lines.each{|l| l = l.split('|'); report[l[0]] ||= {}; report[l[0]][l[1]] = l[2]}
210
- report.each{|file, times| report[file]['duration'] = times['finish'].to_f - times['start'].to_f}
211
- report = report.sort{|a, b| b[1]['duration'] <=> a[1]['duration']}
212
- output = []
213
- report.each{|file, times| output << "%.2f\t#{file}" % times['duration']}
214
- @report_text = output.join("\n")
215
- File.open(report_results_file, 'w'){|f| f.write @report_text}
216
- return report_text
217
- end
218
-
219
- def reported_files
220
- return [] unless File.exists?(report_results_file)
221
- rep = []
222
- File.open(report_results_file, 'r') do |f|
223
- lines = f.read.split("\n")
224
- lines.each{|l| rep << l.split(" ")[1] }
225
- end
226
- return rep
198
+ @event_listeners.each{|l| l.testing_end}
227
199
  end
228
200
 
229
201
  def sort_files_from_report
230
- sorted_files = reported_files
231
- reported_files.each do |f|
232
- @files.push(@files.delete_at(@files.index(f))) if @files.index(f)
202
+ if File.exists? heuristic_file
203
+ report = YAML.load_file(heuristic_file)
204
+ return unless report
205
+ sorted_files = report.sort{ |a,b|
206
+ b[1]['duration'] <=> a[1]['duration']
207
+ }.collect{|tuple| tuple[0]}
208
+
209
+ sorted_files.each do |f|
210
+ @files.push(@files.delete_at(@files.index(f))) if @files.index(f)
211
+ end
233
212
  end
234
213
  end
235
214
 
236
- def report_file
237
- @report_file ||= File.join(Dir.tmpdir, 'hydra_report.txt')
238
- end
239
-
240
- def report_results_file
241
- @report_results_file ||= File.join(Dir.tmpdir, 'hydra_report_results.txt')
215
+ def heuristic_file
216
+ @heuristic_file ||= File.join(Dir.tmpdir, 'hydra_heuristics.yml')
242
217
  end
243
218
  end
244
219
  end
@@ -56,7 +56,7 @@ module Hydra #:nodoc:
56
56
  # t.add_files 'test/functional/**/*_test.rb'
57
57
  # t.add_files 'test/integration/**/*_test.rb'
58
58
  # t.verbose = false # optionally set to true for lots of debug messages
59
- # t.report = true # optionally set to true for a final report of test times
59
+ # t.autosort = false # disable automatic sorting based on runtime of tests
60
60
  # end
61
61
  class TestTask < Hydra::Task
62
62
 
@@ -65,8 +65,8 @@ module Hydra #:nodoc:
65
65
  @name = name
66
66
  @files = []
67
67
  @verbose = false
68
- @report = false
69
68
  @autosort = true
69
+ @listeners = [Hydra::Listener::MinimalOutput.new]
70
70
 
71
71
  yield self if block_given?
72
72
 
@@ -74,9 +74,9 @@ module Hydra #:nodoc:
74
74
 
75
75
  @opts = {
76
76
  :verbose => @verbose,
77
- :report => @report,
78
77
  :autosort => @autosort,
79
- :files => @files
78
+ :files => @files,
79
+ :listeners => @listeners
80
80
  }
81
81
  if @config
82
82
  @opts.merge!(:config => @config)
@@ -92,10 +92,7 @@ module Hydra #:nodoc:
92
92
  def define
93
93
  desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
94
94
  task @name do
95
- $stdout.write "Hydra Testing #{files.inspect}\n"
96
- h = Hydra::Master.new(@opts)
97
- $stdout.write "\n"+h.report_text if @report
98
- $stdout.write "\nHydra Completed\n"
95
+ Hydra::Master.new(@opts)
99
96
  exit(0) #bypass test on_exit output
100
97
  end
101
98
  end
@@ -21,15 +21,13 @@ class MasterTest < Test::Unit::TestCase
21
21
  end
22
22
 
23
23
  should "generate a report" do
24
- Hydra::Master.new(
25
- :files => [test_file],
26
- :report => true
27
- )
24
+ Hydra::Master.new(:files => [test_file])
28
25
  assert File.exists?(target_file)
29
26
  assert_equal "HYDRA", File.read(target_file)
30
- report_file = File.join(Dir.tmpdir, 'hydra_report.txt')
27
+ report_file = File.join(Dir.tmpdir, 'hydra_heuristics.yml')
31
28
  assert File.exists?(report_file)
32
- assert_equal 2, File.read(report_file).split("\n").size
29
+ assert report = YAML.load_file(report_file)
30
+ assert_not_nil report[test_file]
33
31
  end
34
32
 
35
33
  should "run a test 6 times on 1 worker with 2 runners" do
@@ -119,19 +117,13 @@ class MasterTest < Test::Unit::TestCase
119
117
  # ensure b is on remote
120
118
  assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
121
119
 
122
- # fake as if the test got run, so only the sync code is really being tested
123
- fake_result = Hydra::Messages::Worker::Results.new(
124
- :file => 'test_a.rb', :output => '.'
125
- ).serialize.inspect
126
-
127
120
  Hydra::Master.new(
128
121
  :files => ['test_a.rb'],
129
122
  :workers => [{
130
123
  :type => :ssh,
131
124
  :connect => 'localhost',
132
125
  :directory => remote,
133
- :runners => 1,
134
- :command => "ruby -e 'puts #{fake_result}' && exit"
126
+ :runners => 1
135
127
  }],
136
128
  :sync => {
137
129
  :directory => local,
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.12.0
4
+ version: 0.13.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-18 00:00:00 -05:00
12
+ date: 2010-03-25 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -45,6 +45,9 @@ files:
45
45
  - hydra_gray.png
46
46
  - lib/hydra.rb
47
47
  - lib/hydra/hash.rb
48
+ - lib/hydra/listener/abstract.rb
49
+ - lib/hydra/listener/minimal_output.rb
50
+ - lib/hydra/listener/report_generator.rb
48
51
  - lib/hydra/master.rb
49
52
  - lib/hydra/message.rb
50
53
  - lib/hydra/message/master_messages.rb