hydra 0.12.0 → 0.13.0

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