rake 0.9.3.beta.2 → 0.9.3.beta.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rake might be problematic. Click here for more details.

@@ -129,6 +129,7 @@ Issues and bug reports can also be tracked here:
129
129
  * Rake Source Code Repo: http://github.com/jimweirich/rake
130
130
  * Rake Git Repo Clone URL: git://github.com/jimweirich/rake.git
131
131
  * Rake Bug Reports: https://github.com/jimweirich/rake/issues
132
+ * Rake Continuous Build Server: https://travis-ci.org/#!/jimweirich/rake
132
133
 
133
134
  === Presentations and Articles about Rake
134
135
 
@@ -31,7 +31,7 @@ Options are:
31
31
  [<tt>--execute-print</tt> _code_ (-p)]
32
32
  Execute some Ruby code, print the result, and exit.
33
33
 
34
- [<tt>--execute-continue</tt> _code_ (-p)]
34
+ [<tt>--execute-continue</tt> _code_ (-E)]
35
35
  Execute some Ruby code, then continue with normal task processing.
36
36
 
37
37
  [<tt>--help</tt> (-H)]
@@ -49,6 +49,9 @@ Options are:
49
49
  [<tt>--libdir</tt> _directory_ (-I)]
50
50
  Add _directory_ to the list of directories searched for require.
51
51
 
52
+ [<tt>--multitask</tt> (-m)]
53
+ Treat all tasks as multitasks. ('make/drake' semantics)
54
+
52
55
  [<tt>--nosearch</tt> (-N)]
53
56
  Do not search for a Rakefile in parent directories.
54
57
 
@@ -3,10 +3,13 @@ require 'optparse'
3
3
 
4
4
  require 'rake/task_manager'
5
5
  require 'rake/thread_pool'
6
+ require 'rake/thread_history_display'
6
7
  require 'rake/win32'
7
8
 
8
9
  module Rake
9
10
 
11
+ CommandLineOptionError = Class.new(StandardError)
12
+
10
13
  ######################################################################
11
14
  # Rake main application object. When invoking +rake+ from the
12
15
  # command line, a Rake::Application object is created and run.
@@ -64,8 +67,15 @@ module Rake
64
67
  standard_exception_handling do
65
68
  init
66
69
  load_rakefile
70
+ thread_pool.gather_history if options.job_stats == :history
67
71
  top_level
68
72
  thread_pool.join
73
+ if options.job_stats
74
+ stats = thread_pool.statistics
75
+ puts "Maximum active threads: #{stats[:max_active_threads]}"
76
+ puts "Total threads in play: #{stats[:total_threads_in_play]}"
77
+ end
78
+ ThreadHistoryDisplay.new(thread_pool.history).show if options.job_stats == :history
69
79
  end
70
80
  end
71
81
 
@@ -108,8 +118,9 @@ module Rake
108
118
  @options ||= OpenStruct.new
109
119
  end
110
120
 
111
- def thread_pool
112
- @thread_pool ||= ThreadPool.new options.thread_pool_size
121
+ # Return the thread pool used for multithreaded processing.
122
+ def thread_pool # :nodoc:
123
+ @thread_pool ||= ThreadPool.new(options.thread_pool_size||FIXNUM_MAX)
113
124
  end
114
125
 
115
126
  # private ----------------------------------------------------------------
@@ -150,15 +161,15 @@ module Rake
150
161
 
151
162
  # Display the error message that caused the exception.
152
163
  def display_error_message(ex)
153
- $stderr.puts "#{name} aborted!"
154
- $stderr.puts ex.message
164
+ trace "#{name} aborted!"
165
+ trace ex.message
155
166
  if options.backtrace
156
- $stderr.puts ex.backtrace.join("\n")
167
+ trace ex.backtrace.join("\n")
157
168
  else
158
- $stderr.puts Backtrace.collapse(ex.backtrace)
169
+ trace Backtrace.collapse(ex.backtrace)
159
170
  end
160
- $stderr.puts "Tasks: #{ex.chain}" if has_chain?(ex)
161
- $stderr.puts "(See full trace by running task with --trace)" unless options.backtrace
171
+ trace "Tasks: #{ex.chain}" if has_chain?(ex)
172
+ trace "(See full trace by running task with --trace)" unless options.backtrace
162
173
  end
163
174
 
164
175
  # Warn about deprecated usage.
@@ -293,6 +304,11 @@ module Rake
293
304
  end
294
305
  end
295
306
 
307
+ def trace(*str)
308
+ options.trace_output ||= $stderr
309
+ options.trace_output.puts(*str)
310
+ end
311
+
296
312
  def sort_options(options)
297
313
  options.sort_by { |opt|
298
314
  opt.select { |o| o =~ /^-/ }.map { |o| o.downcase }.sort.reverse
@@ -310,9 +326,10 @@ module Rake
310
326
  options.show_all_tasks = value
311
327
  }
312
328
  ],
313
- ['--backtrace', "Enable full backtrace.",
329
+ ['--backtrace [OUT]', "Enable full backtrace. OUT can be stderr (default) or stdout.",
314
330
  lambda { |value|
315
- options.backtrace = value
331
+ options.backtrace = true
332
+ select_trace_output(options, 'backtrace', value)
316
333
  }
317
334
  ],
318
335
  ['--classic-namespace', '-C', "Put Task and FileTask in the top level namespace",
@@ -328,7 +345,7 @@ module Rake
328
345
  ],
329
346
  ['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
330
347
  lambda { |value|
331
- select_tasks_to_show :describe, value, options
348
+ select_tasks_to_show(options, :describe, value)
332
349
  }
333
350
  ],
334
351
  ['--dry-run', '-n', "Do a dry run without executing actions.",
@@ -359,9 +376,22 @@ module Rake
359
376
  "Specifies the maximum number of tasks to execute in parallel. (default:2)",
360
377
  lambda { |value| options.thread_pool_size = [(value || 2).to_i,2].max }
361
378
  ],
379
+ ['--job-stats [LEVEL]',
380
+ "Display job statistics. LEVEL=history displays a complete job list",
381
+ lambda { |value|
382
+ if value =~ /^history/i
383
+ options.job_stats = :history
384
+ else
385
+ options.job_stats = true
386
+ end
387
+ }
388
+ ],
362
389
  ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.",
363
390
  lambda { |value| $:.push(value) }
364
391
  ],
392
+ ['--multitask', '-m', "Treat all tasks as multitasks.",
393
+ lambda { |value| options.always_multitask = true }
394
+ ],
365
395
  ['--no-search', '--nosearch', '-N', "Do not search parent directories for the Rakefile.",
366
396
  lambda { |value| options.nosearch = true }
367
397
  ],
@@ -409,6 +439,11 @@ module Rake
409
439
  options.silent = true
410
440
  }
411
441
  ],
442
+ ['--suppress-backtrace PATTERN', "Suppress backtrace lines matching regexp PATTERN. Ignored if --trace is on.",
443
+ lambda { |value|
444
+ options.suppress_backtrace_pattern = Regexp.new(value)
445
+ }
446
+ ],
412
447
  ['--system', '-g',
413
448
  "Using system wide (global) rakefiles (usually '~/.rake/*.rake').",
414
449
  lambda { |value| options.load_system = true }
@@ -419,13 +454,14 @@ module Rake
419
454
  ],
420
455
  ['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.",
421
456
  lambda { |value|
422
- select_tasks_to_show :tasks, value, options
457
+ select_tasks_to_show(options, :tasks, value)
423
458
  }
424
459
  ],
425
- ['--trace', '-t', "Turn on invoke/execute tracing, enable full backtrace.",
460
+ ['--trace', '-t [OUT]', "Turn on invoke/execute tracing, enable full backtrace. OUT can be stderr (default) or stdout.",
426
461
  lambda { |value|
427
462
  options.trace = true
428
463
  options.backtrace = true
464
+ select_trace_output(options, 'trace', value)
429
465
  Rake.verbose(true)
430
466
  }
431
467
  ],
@@ -440,7 +476,7 @@ module Rake
440
476
  ],
441
477
  ['--where', '-W [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
442
478
  lambda { |value|
443
- select_tasks_to_show :lines, value, options
479
+ select_tasks_to_show(options, :lines, value)
444
480
  options.show_all_tasks = true
445
481
  }
446
482
  ],
@@ -452,16 +488,30 @@ module Rake
452
488
  ])
453
489
  end
454
490
 
455
- def select_tasks_to_show(show_tasks, value, options)
491
+ def select_tasks_to_show(options, show_tasks, value)
456
492
  options.show_tasks = show_tasks
457
493
  options.show_task_pattern = Regexp.new(value || '')
458
494
  Rake::TaskManager.record_task_metadata = true
459
495
  end
460
496
  private :select_tasks_to_show
461
497
 
498
+ def select_trace_output(options, trace_option, value)
499
+ value = value.strip unless value.nil?
500
+ case value
501
+ when 'stdout'
502
+ options.trace_output = $stdout
503
+ when 'stderr', nil
504
+ options.trace_output = $stderr
505
+ else
506
+ fail CommandLineOptionError, "Unrecognized --#{trace_option} option '#{value}'"
507
+ end
508
+ end
509
+ private :select_trace_output
510
+
462
511
  # Read and handle the command line options.
463
512
  def handle_options
464
513
  options.rakelib = ['rakelib']
514
+ options.trace_output = $stderr
465
515
 
466
516
  OptionParser.new do |opts|
467
517
  opts.banner = "rake [-f rakefile] {options} targets..."
@@ -632,5 +682,9 @@ module Rake
632
682
 
633
683
  backtrace.find { |str| str =~ re } || ''
634
684
  end
685
+
686
+ private
687
+ FIXNUM_MAX = (2**(0.size * 8 - 2) - 1) # :nodoc:
688
+
635
689
  end
636
690
  end
@@ -1,15 +1,17 @@
1
1
  module Rake
2
2
  module Backtrace
3
- SUPPRESSED_PATHS = [
4
- RbConfig::CONFIG["prefix"],
5
- File.join(File.dirname(__FILE__), ".."),
6
- ].map { |f| Regexp.quote(File.expand_path(f)) }
3
+ SUPPRESSED_PATHS =
4
+ RbConfig::CONFIG.values_at(*RbConfig::CONFIG.
5
+ keys.grep(/(prefix|libdir)/)) + [
6
+ File.join(File.dirname(__FILE__), ".."),
7
+ ].map { |f| Regexp.quote(File.expand_path(f)) }
7
8
 
8
9
  SUPPRESS_PATTERN = %r!(\A#{SUPPRESSED_PATHS.join('|')}|bin/rake:\d+)!
9
10
 
10
- # Elide backtrace elements which match one of SUPPRESS_PATHS.
11
11
  def self.collapse(backtrace)
12
- backtrace.reject { |elem| elem =~ SUPPRESS_PATTERN }
12
+ pattern = Rake.application.options.suppress_backtrace_pattern ||
13
+ SUPPRESS_PATTERN
14
+ backtrace.reject { |elem| elem =~ pattern }
13
15
  end
14
16
  end
15
17
  end
@@ -52,8 +52,8 @@ module Rake
52
52
 
53
53
  # Declare a file creation task.
54
54
  # (Mainly used for the directory command).
55
- def file_create(args, &block)
56
- Rake::FileCreationTask.define_task(args, &block)
55
+ def file_create(*args, &block)
56
+ Rake::FileCreationTask.define_task(*args, &block)
57
57
  end
58
58
 
59
59
  # Declare a set of files tasks to create the given directories on
@@ -62,12 +62,15 @@ module Rake
62
62
  # Example:
63
63
  # directory "testdata/doc"
64
64
  #
65
- def directory(dir)
65
+ def directory(*args, &block)
66
+ result = file_create(*args, &block)
67
+ dir, _ = *Rake.application.resolve_args(args)
66
68
  Rake.each_dir_parent(dir) do |d|
67
69
  file_create d do |t|
68
70
  mkdir_p t.name if ! File.exist?(t.name)
69
71
  end
70
72
  end
73
+ result
71
74
  end
72
75
 
73
76
  # Declare a task that performs its prerequisites in
@@ -172,5 +175,8 @@ module Rake
172
175
  extend FileUtilsExt
173
176
  end
174
177
 
178
+ # Extend the main object with the DSL commands. This allows top-level
179
+ # calls to task, etc. to work from a Rakefile without polluting the
180
+ # object inheritance tree.
175
181
  self.extend Rake::DSL
176
182
  include Rake::DeprecatedObjectDSL unless defined? Rake::REDUCE_COMPAT
@@ -5,13 +5,8 @@ module Rake
5
5
  #
6
6
  class MultiTask < Task
7
7
  private
8
- def invoke_prerequisites(args, invocation_chain)
9
- futures = @prerequisites.collect do |p|
10
- application.thread_pool.future(p) do |r|
11
- application[r, @scope].invoke_with_call_chain(args, invocation_chain)
12
- end
13
- end
14
- futures.each { |f| f.call }
8
+ def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
9
+ invoke_prerequisites_concurrently(task_args, invocation_chain)
15
10
  end
16
11
  end
17
12
 
@@ -25,6 +25,14 @@ module Rake
25
25
  load(path)
26
26
  end
27
27
 
28
+ # Add files to the rakelib list
29
+ def add_rakelib(*files)
30
+ application.options.rakelib ||= []
31
+ files.each do |file|
32
+ application.options.rakelib << file
33
+ end
34
+ end
35
+
28
36
  # Get a sorted list of files matching the pattern. This method
29
37
  # should be prefered to Dir[pattern] and Dir.glob[pattern] because
30
38
  # the files returned are guaranteed to be sorted.
@@ -158,7 +158,7 @@ module Rake
158
158
  new_chain = InvocationChain.append(self, invocation_chain)
159
159
  @lock.synchronize do
160
160
  if application.options.trace
161
- $stderr.puts "** Invoke #{name} #{format_trace_flags}"
161
+ application.trace "** Invoke #{name} #{format_trace_flags}"
162
162
  end
163
163
  return if @already_invoked
164
164
  @already_invoked = true
@@ -179,10 +179,24 @@ module Rake
179
179
 
180
180
  # Invoke all the prerequisites of a task.
181
181
  def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
182
- prerequisite_tasks.each { |prereq|
183
- prereq_args = task_args.new_scope(prereq.arg_names)
184
- prereq.invoke_with_call_chain(prereq_args, invocation_chain)
185
- }
182
+ if application.options.always_multitask
183
+ invoke_prerequisites_concurrently(task_args, invocation_chain)
184
+ else
185
+ prerequisite_tasks.each { |prereq|
186
+ prereq_args = task_args.new_scope(prereq.arg_names)
187
+ prereq.invoke_with_call_chain(prereq_args, invocation_chain)
188
+ }
189
+ end
190
+ end
191
+
192
+ # Invoke all the prerequisites of a task in parallel.
193
+ def invoke_prerequisites_concurrently(args, invocation_chain) # :nodoc:
194
+ futures = @prerequisites.collect do |p|
195
+ application.thread_pool.future(p) do |r|
196
+ application[r, @scope].invoke_with_call_chain(args, invocation_chain)
197
+ end
198
+ end
199
+ futures.each { |f| f.call }
186
200
  end
187
201
 
188
202
  # Format the trace flags for display.
@@ -198,11 +212,11 @@ module Rake
198
212
  def execute(args=nil)
199
213
  args ||= EMPTY_TASK_ARGS
200
214
  if application.options.dryrun
201
- $stderr.puts "** Execute (dry run) #{name}"
215
+ application.trace "** Execute (dry run) #{name}"
202
216
  return
203
217
  end
204
218
  if application.options.trace
205
- $stderr.puts "** Execute #{name}"
219
+ application.trace "** Execute #{name}"
206
220
  end
207
221
  application.enhance_with_matching_rule(name) if @actions.empty?
208
222
  @actions.each do |act|
@@ -238,7 +238,7 @@ module Rake
238
238
  end
239
239
 
240
240
  def trace_rule(level, message)
241
- $stderr.puts "#{" "*level}#{message}" if Rake.application.options.trace_rules
241
+ options.trace_output.puts "#{" "*level}#{message}" if Rake.application.options.trace_rules
242
242
  end
243
243
 
244
244
  # Attempt to create a rule given the list of prerequisites.
@@ -0,0 +1,45 @@
1
+ module Rake
2
+
3
+ class ThreadHistoryDisplay
4
+ attr_reader :stats, :items, :threads
5
+ private :stats, :items, :threads
6
+
7
+ def initialize(stats)
8
+ @stats = stats
9
+ @items = { :_seq_ => 1 }
10
+ @threads = { :_seq_ => "A" }
11
+ end
12
+
13
+ def show
14
+ puts "Job History:"
15
+ stats.each do |stat|
16
+ stat[:data] ||= []
17
+ rename(stat, :thread, threads)
18
+ rename(stat[:data], :item_id, items)
19
+ rename(stat[:data], :new_thread, threads)
20
+ rename(stat[:data], :deleted_thread, threads)
21
+ printf("%8d %2s %-20s %s\n",
22
+ (stat[:time] * 1_000_000).round,
23
+ stat[:thread],
24
+ stat[:event],
25
+ stat[:data].map { |k,v| "#{k}:#{v}" }.join(" "))
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def rename(hash, key, renames)
32
+ if hash && hash[key]
33
+ original = hash[key]
34
+ value = renames[original]
35
+ unless value
36
+ value = renames[:_seq_]
37
+ renames[:_seq_] = renames[:_seq_].succ
38
+ renames[original] = value
39
+ end
40
+ hash[key] = value
41
+ end
42
+ end
43
+ end
44
+
45
+ end
@@ -3,24 +3,31 @@ require 'set'
3
3
 
4
4
  module Rake
5
5
 
6
- class ThreadPool
6
+ class ThreadPool # :nodoc: all
7
7
 
8
8
  # Creates a ThreadPool object.
9
- # The parameter is the size of the pool. By default, the pool uses unlimited threads.
10
- def initialize(thread_count=nil)
11
- @max_thread_count = [(thread_count||FIXNUM_MAX), 0].max
9
+ # The parameter is the size of the pool.
10
+ def initialize(thread_count)
11
+ @max_active_threads = [thread_count, 0].max
12
12
  @threads = Set.new
13
13
  @threads_mon = Monitor.new
14
14
  @queue = Queue.new
15
15
  @join_cond = @threads_mon.new_cond
16
+
17
+ @history_start_time = nil
18
+ @history = []
19
+ @history_mon = Monitor.new
20
+ @total_threads_in_play = 0
16
21
  end
17
-
18
- # Creates a future to be executed in the ThreadPool.
19
- # The args are passed to the block when executing (similarly to Thread#new)
20
- # The return value is a Proc which may or may not be already executing in
21
- # another thread. Calling Proc#call will sleep the current thread until
22
- # the future is finished and will return the result (or raise an Exception
23
- # thrown from the future)
22
+
23
+ # Creates a future executed by the +ThreadPool+.
24
+ #
25
+ # The args are passed to the block when executing (similarly to
26
+ # <tt>Thread#new</tt>) The return value is an object representing
27
+ # a future which has been created and added to the queue in the
28
+ # pool. Sending <tt>#value</tt> to the object will sleep the
29
+ # current thread until the future is finished and will return the
30
+ # result (or raise an exception thrown from the future)
24
31
  def future(*args,&block)
25
32
  # capture the local args for the block (like Thread#start)
26
33
  local_args = args.collect { |a| begin; a.dup; rescue; a; end }
@@ -34,7 +41,7 @@ module Rake
34
41
  unless promise_result.equal?(NOT_SET) && promise_error.equal?(NOT_SET)
35
42
  return promise_error.equal?(NOT_SET) ? promise_result : raise(promise_error)
36
43
  end
37
-
44
+
38
45
  # try to get the lock and execute the promise, otherwise, sleep.
39
46
  if promise_mutex.try_lock
40
47
  if promise_result.equal?(NOT_SET) && promise_error.equal?(NOT_SET)
@@ -48,86 +55,135 @@ module Rake
48
55
  end
49
56
  promise_mutex.unlock
50
57
  else
51
- # Even if we didn't get the lock, we need to sleep until the promise has
52
- # finished executing. If, however, the current thread is part of the thread
53
- # pool, we need to free up a new thread in the pool so there will
54
- # always be a thread doing work.
58
+ # Even if we didn't get the lock, we need to sleep until the
59
+ # promise has finished executing. If, however, the current
60
+ # thread is part of the thread pool, we need to free up a
61
+ # new thread in the pool so there will always be a thread
62
+ # doing work.
55
63
 
56
- wait_for_promise = lambda { promise_mutex.synchronize{} }
64
+ wait_for_promise = lambda {
65
+ stat :waiting, item_id: promise.object_id
66
+ promise_mutex.synchronize {}
67
+ stat :continue, item_id: promise.object_id
68
+ }
57
69
 
58
70
  unless @threads_mon.synchronize { @threads.include? Thread.current }
59
71
  wait_for_promise.call
60
72
  else
61
- @threads_mon.synchronize { @max_thread_count += 1 }
73
+ @threads_mon.synchronize { @max_active_threads += 1 }
62
74
  start_thread
63
75
  wait_for_promise.call
64
- @threads_mon.synchronize { @max_thread_count -= 1 }
76
+ @threads_mon.synchronize { @max_active_threads -= 1 }
65
77
  end
66
78
  end
67
79
  promise_error.equal?(NOT_SET) ? promise_result : raise(promise_error)
68
80
  end
69
81
 
82
+ def promise.value
83
+ call
84
+ end
85
+
70
86
  @queue.enq promise
87
+ stat :item_queued, item_id: promise.object_id
71
88
  start_thread
72
89
  promise
73
90
  end
74
-
91
+
75
92
  # Waits until the queue of futures is empty and all threads have exited.
76
93
  def join
77
94
  @threads_mon.synchronize do
78
95
  begin
79
96
  @join_cond.wait unless @threads.empty?
80
97
  rescue Exception => e
81
- STDERR.puts e
82
- STDERR.print "Queue contains #{@queue.size} items. Thread pool contains #{@threads.count} threads\n"
83
- STDERR.print "Current Thread #{Thread.current} status = #{Thread.current.status}\n"
84
- STDERR.puts e.backtrace.join("\n")
98
+ $stderr.puts e
99
+ $stderr.print "Queue contains #{@queue.size} items. Thread pool contains #{@threads.count} threads\n"
100
+ $stderr.print "Current Thread #{Thread.current} status = #{Thread.current.status}\n"
101
+ $stderr.puts e.backtrace.join("\n")
85
102
  @threads.each do |t|
86
- STDERR.print "Thread #{t} status = #{t.status}\n"
87
- STDERR.puts t.backtrace.join("\n") if t.respond_to? :backtrace
103
+ $stderr.print "Thread #{t} status = #{t.status}\n"
104
+ $stderr.puts t.backtrace.join("\n") if t.respond_to? :backtrace
88
105
  end
89
106
  raise e
90
107
  end
91
108
  end
92
109
  end
93
110
 
94
- private
95
- def start_thread
111
+ # Enable the gathering of history events.
112
+ def gather_history #:nodoc:
113
+ @history_start_time = Time.now if @history_start_time.nil?
114
+ end
115
+
116
+ # Return a array of history events for the thread pool.
117
+ #
118
+ # History gathering must be enabled to be able to see the events
119
+ # (see #gather_history). Best to call this when the job is
120
+ # complete (i.e. after ThreadPool#join is called).
121
+ def history # :nodoc:
122
+ @history_mon.synchronize { @history.dup }
123
+ end
124
+
125
+ # Return a hash of always collected statistics for the thread pool.
126
+ def statistics # :nodoc:
127
+ {
128
+ total_threads_in_play: @total_threads_in_play,
129
+ max_active_threads: @max_active_threads,
130
+ }
131
+ end
132
+
133
+ private
134
+
135
+ def start_thread # :nodoc:
96
136
  @threads_mon.synchronize do
97
- next unless @threads.count < @max_thread_count
137
+ next unless @threads.count < @max_active_threads
98
138
 
99
- @threads << Thread.new do
139
+ t = Thread.new do
100
140
  begin
101
- while @threads.count <= @max_thread_count && !@queue.empty? do
102
- # Even though we just asked if the queue was empty,
103
- # it still could have had an item which by this statement is now gone.
104
- # For this reason we pass true to Queue#deq because we will sleep
105
- # indefinitely if it is empty.
106
- @queue.deq(true).call
141
+ while @threads.count <= @max_active_threads && !@queue.empty? do
142
+ # Even though we just asked if the queue was empty, it
143
+ # still could have had an item which by this statement
144
+ # is now gone. For this reason we pass true to Queue#deq
145
+ # because we will sleep indefinitely if it is empty.
146
+ block = @queue.deq(true)
147
+ stat :item_dequeued, item_id: block.object_id
148
+ block.call
107
149
  end
108
150
  rescue ThreadError # this means the queue is empty
109
151
  ensure
110
152
  @threads_mon.synchronize do
111
153
  @threads.delete Thread.current
154
+ stat :thread_deleted, deleted_thread: Thread.current.object_id, thread_count: @threads.count
112
155
  @join_cond.broadcast if @threads.empty?
113
156
  end
114
157
  end
115
158
  end
159
+ @threads << t
160
+ stat :thread_created, new_thread: t.object_id, thread_count: @threads.count
161
+ @total_threads_in_play = @threads.count if @threads.count > @total_threads_in_play
116
162
  end
117
163
  end
118
-
164
+
165
+ def stat(event, data=nil) # :nodoc:
166
+ return if @history_start_time.nil?
167
+ info = {
168
+ event: event,
169
+ data: data,
170
+ time: (Time.now-@history_start_time),
171
+ thread: Thread.current.object_id,
172
+ }
173
+ @history_mon.synchronize { @history << info }
174
+ end
175
+
119
176
  # for testing only
120
-
121
- def __queue__
177
+
178
+ def __queue__ # :nodoc:
122
179
  @queue
123
180
  end
124
-
125
- def __threads__
181
+
182
+ def __threads__ # :nodoc:
126
183
  @threads.dup
127
184
  end
128
-
129
- NOT_SET = Object.new.freeze
130
- FIXNUM_MAX = (2**(0.size * 8 - 2) - 1) # FIXNUM_MAX
185
+
186
+ NOT_SET = Object.new.freeze # :nodoc:
131
187
  end
132
-
188
+
133
189
  end
@@ -5,7 +5,7 @@ module Rake
5
5
  MINOR = 9,
6
6
  BUILD = 3,
7
7
  'beta',
8
- BETA = 2,
8
+ BETA = 3,
9
9
  ]
10
10
  end
11
11
  VERSION = Version::NUMBERS.join('.')
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
3
 
3
4
  begin
4
5
  gem 'minitest'
@@ -34,6 +34,7 @@ class TestRakeApplicationOptions < Rake::TestCase
34
34
  assert_nil opts.dryrun
35
35
  assert_nil opts.ignore_system
36
36
  assert_nil opts.load_system
37
+ assert_nil opts.always_multitask
37
38
  assert_nil opts.nosearch
38
39
  assert_equal ['rakelib'], opts.rakelib
39
40
  assert_nil opts.show_prereqs
@@ -132,6 +133,12 @@ class TestRakeApplicationOptions < Rake::TestCase
132
133
  $:.delete('xx')
133
134
  end
134
135
 
136
+ def test_multitask
137
+ flags('--multitask', '-m') do |opts|
138
+ assert_equal opts.always_multitask, true
139
+ end
140
+ end
141
+
135
142
  def test_rakefile
136
143
  flags(['--rakefile', 'RF'], ['--rakefile=RF'], ['-f', 'RF'], ['-fRF']) do |opts|
137
144
  assert_equal ['RF'], @app.instance_eval { @rakefiles }
@@ -213,19 +220,74 @@ class TestRakeApplicationOptions < Rake::TestCase
213
220
  flags('--trace', '-t') do |opts|
214
221
  assert opts.trace, "should enable trace option"
215
222
  assert opts.backtrace, "should enabled backtrace option"
223
+ assert_equal $stderr, opts.trace_output
216
224
  assert Rake::FileUtilsExt.verbose_flag
217
225
  assert ! Rake::FileUtilsExt.nowrite_flag
218
226
  end
219
227
  end
220
228
 
229
+ def test_trace_with_stdout
230
+ flags('--trace=stdout', '-tstdout', '-t stdout') do |opts|
231
+ assert opts.trace, "should enable trace option"
232
+ assert opts.backtrace, "should enabled backtrace option"
233
+ assert_equal $stdout, opts.trace_output
234
+ assert Rake::FileUtilsExt.verbose_flag
235
+ assert ! Rake::FileUtilsExt.nowrite_flag
236
+ end
237
+ end
238
+
239
+ def test_trace_with_stderr
240
+ flags('--trace=stderr', '-tstderr', '-t stderr') do |opts|
241
+ assert opts.trace, "should enable trace option"
242
+ assert opts.backtrace, "should enabled backtrace option"
243
+ assert_equal $stderr, opts.trace_output
244
+ assert Rake::FileUtilsExt.verbose_flag
245
+ assert ! Rake::FileUtilsExt.nowrite_flag
246
+ end
247
+ end
248
+
249
+ def test_trace_with_error
250
+ ex = assert_raises(Rake::CommandLineOptionError) do
251
+ flags('--trace=xyzzy') do |opts| end
252
+ end
253
+ assert_match(/un(known|recognized).*\btrace\b.*xyzzy/i, ex.message)
254
+ end
255
+
256
+
221
257
  def test_backtrace
222
258
  flags('--backtrace') do |opts|
223
259
  assert opts.backtrace, "should enable backtrace option"
260
+ assert_equal $stderr, opts.trace_output
224
261
  assert ! opts.trace, "should not enable trace option"
225
262
  assert ! Rake::FileUtilsExt.verbose_flag
226
263
  end
227
264
  end
228
265
 
266
+ def test_backtrace_with_stdout
267
+ flags('--backtrace=stdout') do |opts|
268
+ assert opts.backtrace, "should enable backtrace option"
269
+ assert_equal $stdout, opts.trace_output
270
+ assert ! opts.trace, "should not enable trace option"
271
+ assert ! Rake::FileUtilsExt.verbose_flag
272
+ end
273
+ end
274
+
275
+ def test_backtrace_with_stderr
276
+ flags('--backtrace=stderr') do |opts|
277
+ assert opts.backtrace, "should enable backtrace option"
278
+ assert_equal $stderr, opts.trace_output
279
+ assert ! opts.trace, "should not enable trace option"
280
+ assert ! Rake::FileUtilsExt.verbose_flag
281
+ end
282
+ end
283
+
284
+ def test_backtrace_with_error
285
+ ex = assert_raises(Rake::CommandLineOptionError) do
286
+ flags('--backtrace=xyzzy') do |opts| end
287
+ end
288
+ assert_match(/un(known|recognized).*\bbacktrace\b.*xyzzy/i, ex.message)
289
+ end
290
+
229
291
  def test_trace_rules
230
292
  flags('--rules') do |opts|
231
293
  assert opts.trace_rules
@@ -345,6 +407,17 @@ class TestRakeApplicationOptions < Rake::TestCase
345
407
  assert '12', ENV['TESTKEY']
346
408
  end
347
409
 
410
+ def test_rake_explicit_task_library
411
+ Rake.add_rakelib 'app/task', 'other'
412
+
413
+ libs = Rake.application.options.rakelib
414
+
415
+ assert libs.include?("app/task")
416
+ assert libs.include?("other")
417
+ end
418
+
419
+ private
420
+
348
421
  def flags(*sets)
349
422
  sets.each do |set|
350
423
  ARGV.clear
@@ -46,4 +46,22 @@ class TestRakeBacktrace < Rake::TestCase
46
46
  assert_match %r!\A#{Regexp.quote Dir.pwd}/Rakefile:3!, lines[3]
47
47
  assert_match %r!\ATasks:!, lines[4]
48
48
  end
49
+
50
+ def test_suppress_option
51
+ rakefile %q{
52
+ task :baz do
53
+ raise "bazzz!"
54
+ end
55
+ }
56
+
57
+ lines = rake("baz").split("\n")
58
+ assert_equal "rake aborted!", lines[0]
59
+ assert_equal "bazzz!", lines[1]
60
+ assert_match %r!Rakefile!, lines[2]
61
+
62
+ lines = rake("--suppress-backtrace", "R.k.file", "baz").split("\n")
63
+ assert_equal "rake aborted!", lines[0]
64
+ assert_equal "bazzz!", lines[1]
65
+ refute_match %r!Rakefile!, lines[2]
66
+ end
49
67
  end
@@ -38,4 +38,20 @@ class TestRakeDirectoryTask < Rake::TestCase
38
38
  assert_nil Task['c:/a/b'].comment
39
39
  end
40
40
  end
41
+
42
+ def test_can_use_blocks
43
+ runlist = []
44
+
45
+ t1 = directory("a/b/c" => :t2) { |t| runlist << t.name }
46
+ t2 = task(:t2) { |t| runlist << t.name }
47
+
48
+ verbose(false) {
49
+ t1.invoke
50
+ }
51
+
52
+ assert_equal Task["a/b/c"], t1
53
+ assert_equal FileCreationTask, Task["a/b/c"].class
54
+ assert_equal ["t2", "a/b/c"], runlist
55
+ assert File.directory?("a/b/c")
56
+ end
41
57
  end
@@ -241,6 +241,38 @@ class TestRakeTask < Rake::TestCase
241
241
  assert_in_delta now + 10, a.timestamp, 0.1, 'computer too slow?'
242
242
  end
243
243
 
244
+ def test_always_multitask
245
+ mx = Mutex.new
246
+ result = []
247
+
248
+ t_a = task(:a) do |t|
249
+ sleep 0.02
250
+ mx.synchronize{ result << t.name }
251
+ end
252
+
253
+ t_b = task(:b) do |t|
254
+ mx.synchronize{ result << t.name }
255
+ end
256
+
257
+ t_c = task(:c => [:a,:b]) do |t|
258
+ mx.synchronize{ result << t.name }
259
+ end
260
+
261
+ t_c.invoke
262
+
263
+ # task should always run in order
264
+ assert_equal ['a', 'b', 'c'], result
265
+
266
+ [t_a, t_b, t_c].each { |t| t.reenable }
267
+ result.clear
268
+
269
+ Rake.application.options.always_multitask = true
270
+ t_c.invoke
271
+
272
+ # with multitask, task 'b' should grab the mutex first
273
+ assert_equal ['b', 'a', 'c'], result
274
+ end
275
+
244
276
  def test_investigation_output
245
277
  t1 = task(:t1 => [:t2, :t3]) { |t| runlist << t.name; 3321 }
246
278
  task(:t2)
@@ -9,19 +9,19 @@ class TestRakeTestThreadPool < Rake::TestCase
9
9
  pool = ThreadPool.new(0)
10
10
  f = pool.future{Thread.current}
11
11
  pool.join
12
- assert_equal Thread.current, f.call
12
+ assert_equal Thread.current, f.value
13
13
  end
14
14
 
15
15
  def test_pool_executes_in_other_thread_for_pool_of_size_one
16
16
  pool = ThreadPool.new(1)
17
17
  f = pool.future{Thread.current}
18
18
  pool.join
19
- refute_equal Thread.current, f.call
19
+ refute_equal Thread.current, f.value
20
20
  end
21
21
 
22
22
  def test_pool_executes_in_two_other_threads_for_pool_of_size_two
23
23
  pool = ThreadPool.new(2)
24
- threads = 2.times.collect{ pool.future{ sleep 0.1; Thread.current } }.each{|f|f.call}
24
+ threads = 2.times.collect{ pool.future{ sleep 0.1; Thread.current } }.each{|f|f.value}
25
25
 
26
26
  refute_equal threads[0], threads[1]
27
27
  refute_equal Thread.current, threads[0]
@@ -79,11 +79,11 @@ class TestRakeTestThreadPool < Rake::TestCase
79
79
 
80
80
  deep_exception_block = lambda do |count|
81
81
  next raise Exception.new if ( count < 1 )
82
- pool.future(count-1, &deep_exception_block).call
82
+ pool.future(count-1, &deep_exception_block).value
83
83
  end
84
84
 
85
85
  assert_raises(Exception) do
86
- pool.future(2, &deep_exception_block).call
86
+ pool.future(2, &deep_exception_block).value
87
87
  end
88
88
 
89
89
  end
@@ -94,7 +94,7 @@ class TestRakeTestThreadPool < Rake::TestCase
94
94
  pool = ThreadPool.new(2)
95
95
  initial_sleep_time = 0.2
96
96
  future1 = pool.future { sleep initial_sleep_time }
97
- dependent_futures = 5.times.collect { pool.future{ future1.call } }
97
+ dependent_futures = 5.times.collect { pool.future{ future1.value } }
98
98
  future2 = pool.future { sleep initial_sleep_time }
99
99
  future3 = pool.future { sleep 0.01 }
100
100
 
@@ -107,7 +107,7 @@ class TestRakeTestThreadPool < Rake::TestCase
107
107
  # future 3 is in the queue because there aren't enough active threads to work on it.
108
108
  assert_equal pool.__send__(:__queue__).size, 1
109
109
 
110
- [future1, dependent_futures, future2, future3].flatten.each { |f| f.call }
110
+ [future1, dependent_futures, future2, future3].flatten.each { |f| f.value }
111
111
  pool.join
112
112
  end
113
113
 
@@ -115,12 +115,12 @@ class TestRakeTestThreadPool < Rake::TestCase
115
115
  pool = ThreadPool.new(5)
116
116
 
117
117
  common_dependency_a = pool.future { sleep 0.2 }
118
- futures_a = 10.times.collect { pool.future{ common_dependency_a.call; sleep(rand() * 0.01) } }
118
+ futures_a = 10.times.collect { pool.future{ common_dependency_a.value; sleep(rand() * 0.01) } }
119
119
 
120
- common_dependency_b = pool.future { futures_a.each { |f| f.call } }
121
- futures_b = 10.times.collect { pool.future{ common_dependency_b.call; sleep(rand() * 0.01) } }
120
+ common_dependency_b = pool.future { futures_a.each { |f| f.value } }
121
+ futures_b = 10.times.collect { pool.future{ common_dependency_b.value; sleep(rand() * 0.01) } }
122
122
 
123
- futures_b.each{|f|f.call}
123
+ futures_b.each{|f|f.value}
124
124
  pool.join
125
125
  end
126
126
 
@@ -135,9 +135,9 @@ class TestRakeTestThreadPool < Rake::TestCase
135
135
  pool.future do
136
136
  b.times.collect do
137
137
  pool.future { sleep rand * 0.001; c }
138
- end.inject(0) { |m,f| m+f.call }
138
+ end.inject(0) { |m,f| m+f.value }
139
139
  end
140
- end.inject(0) { |m,f| m+f.call }
140
+ end.inject(0) { |m,f| m+f.value }
141
141
 
142
142
  assert_equal( (a*b*c), result )
143
143
  pool.join
@@ -0,0 +1,91 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ require 'rake/thread_history_display'
4
+
5
+ class TestThreadHistoryDisplay < Rake::TestCase
6
+ def setup
7
+ super
8
+ @time = 1000000
9
+ @stats = []
10
+ @display = Rake::ThreadHistoryDisplay.new(@stats)
11
+ end
12
+
13
+ def test_banner
14
+ out, _ = capture_io do
15
+ @display.show
16
+ end
17
+ assert_match(/Job History/i, out)
18
+ end
19
+
20
+ def test_item_queued
21
+ @stats << event(:item_queued, item_id: 123)
22
+ out, _ = capture_io do
23
+ @display.show
24
+ end
25
+ assert_match(/^ *1000000 +A +item_queued +item_id:1$/, out)
26
+ end
27
+
28
+ def test_item_dequeued
29
+ @stats << event(:item_dequeued, item_id: 123)
30
+ out, _ = capture_io do
31
+ @display.show
32
+ end
33
+ assert_match(/^ *1000000 +A +item_dequeued +item_id:1$/, out)
34
+ end
35
+
36
+ def test_multiple_items
37
+ @stats << event(:item_queued, item_id: 123)
38
+ @stats << event(:item_queued, item_id: 124)
39
+ out, _ = capture_io do
40
+ @display.show
41
+ end
42
+ assert_match(/^ *1000000 +A +item_queued +item_id:1$/, out)
43
+ assert_match(/^ *1000001 +A +item_queued +item_id:2$/, out)
44
+ end
45
+
46
+ def test_waiting
47
+ @stats << event(:waiting, item_id: 123)
48
+ out, _ = capture_io do
49
+ @display.show
50
+ end
51
+ assert_match(/^ *1000000 +A +waiting +item_id:1$/, out)
52
+ end
53
+
54
+ def test_continue
55
+ @stats << event(:continue, item_id: 123)
56
+ out, _ = capture_io do
57
+ @display.show
58
+ end
59
+ assert_match(/^ *1000000 +A +continue +item_id:1$/, out)
60
+ end
61
+
62
+ def test_thread_deleted
63
+ @stats << event(:thread_deleted, deleted_thread: 123456, thread_count: 12)
64
+ out, _ = capture_io do
65
+ @display.show
66
+ end
67
+ assert_match(/^ *1000000 +A +thread_deleted +deleted_thread:B +thread_count:12$/, out)
68
+ end
69
+
70
+ def test_thread_created
71
+ @stats << event(:thread_created, new_thread: 123456, thread_count: 13)
72
+ out, _ = capture_io do
73
+ @display.show
74
+ end
75
+ assert_match(/^ *1000000 +A +thread_created +new_thread:B +thread_count:13$/, out)
76
+ end
77
+
78
+ private
79
+
80
+ def event(type, data={})
81
+ result = {
82
+ event: type,
83
+ time: @time / 1_000_000.0,
84
+ data: data,
85
+ thread: Thread.current.object_id
86
+ }
87
+ @time += 1
88
+ result
89
+ end
90
+
91
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rake
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3.beta.2
4
+ version: 0.9.3.beta.3
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-22 00:00:00.000000000 Z
12
+ date: 2012-10-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -120,6 +120,7 @@ files:
120
120
  - lib/rake/task_manager.rb
121
121
  - lib/rake/tasklib.rb
122
122
  - lib/rake/testtask.rb
123
+ - lib/rake/thread_history_display.rb
123
124
  - lib/rake/thread_pool.rb
124
125
  - lib/rake/version.rb
125
126
  - lib/rake/win32.rb
@@ -168,6 +169,7 @@ files:
168
169
  - test/test_rake_top_level_functions.rb
169
170
  - test/test_rake_win32.rb
170
171
  - test/test_sys.rb
172
+ - test/test_thread_history_display.rb
171
173
  - doc/command_line_usage.rdoc
172
174
  - doc/example/Rakefile1
173
175
  - doc/example/Rakefile2