drake 0.8.4.1.1.0 → 0.8.4.1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.drake +4 -0
- data/README +2 -85
- data/Rakefile +8 -15
- data/doc/command_line_usage.rdoc +9 -0
- data/doc/parallel.rdoc +112 -0
- data/doc/rakefile.rdoc +6 -0
- data/install.rb +1 -1
- data/lib/rake.rb +59 -96
- data/lib/rake/parallel.rb +137 -32
- data/test/filecreation.rb +12 -0
- data/test/parallel_setup.rb +3 -0
- data/test/rake_test_setup.rb +21 -0
- data/test/serial_setup.rb +3 -0
- data/test/test_application.rb +8 -0
- data/test/test_definitions.rb +6 -2
- data/test/test_file_task.rb +49 -3
- data/test/test_multitask.rb +1 -1
- data/test/test_parallel.rb +177 -38
- data/test/test_require.rb +3 -0
- data/test/test_rules.rb +4 -5
- data/test/test_task_manager.rb +1 -2
- data/test/test_tasks.rb +24 -12
- metadata +25 -21
- data/lib/rake/comp_tree/algorithm.rb +0 -172
- data/lib/rake/comp_tree/comp_tree.rb +0 -42
- data/lib/rake/comp_tree/driver.rb +0 -153
- data/lib/rake/comp_tree/error.rb +0 -23
- data/lib/rake/comp_tree/node.rb +0 -164
- data/test/Rakefile.seq +0 -20
- data/test/Rakefile.simple +0 -18
- data/test/parallel.rb +0 -4
- data/test/single_threaded.rb +0 -3
data/lib/rake/parallel.rb
CHANGED
@@ -1,43 +1,148 @@
|
|
1
|
+
#
|
2
|
+
# Parallel task execution for Rake.
|
3
|
+
#
|
4
|
+
# The comp_tree package is used to create a computation tree which
|
5
|
+
# executes Rake tasks.
|
6
|
+
#
|
7
|
+
# Tasks are first collected with a single-threaded dry run. This
|
8
|
+
# expands file rules, resolves prequisite names and finds task
|
9
|
+
# arguments. A computation tree is then built with the gathered
|
10
|
+
# tasks.
|
11
|
+
#
|
12
|
+
# Note that prerequisites are context-dependent; it is therefore not
|
13
|
+
# possible to create a 1-to-1 mapping between Rake::Tasks and
|
14
|
+
# CompTree::Nodes.
|
15
|
+
#
|
16
|
+
# Author: James M. Lawrence <quixoticsycophant@gmail.com>
|
17
|
+
#
|
1
18
|
|
2
|
-
require '
|
19
|
+
require 'comp_tree'
|
3
20
|
|
4
21
|
module Rake
|
5
|
-
module
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
}
|
18
|
-
}
|
22
|
+
module Parallel #:nodoc:
|
23
|
+
class Driver
|
24
|
+
# Tasks collected during the dry-run phase.
|
25
|
+
attr_reader :tasks
|
26
|
+
|
27
|
+
# Prevent invoke inside invoke.
|
28
|
+
attr_reader :mutex
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@tasks = Hash.new
|
32
|
+
@mutex = Mutex.new
|
33
|
+
end
|
19
34
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
35
|
+
#
|
36
|
+
# Top-level parallel invocation.
|
37
|
+
#
|
38
|
+
# Called from Task#invoke (routed through Task#invoke_parallel).
|
39
|
+
#
|
40
|
+
def invoke(threads, task, *task_args)
|
41
|
+
if @mutex.try_lock
|
42
|
+
begin
|
43
|
+
@tasks.clear
|
44
|
+
|
45
|
+
# dry run task collector
|
46
|
+
task.invoke_serial(*task_args)
|
47
|
+
|
48
|
+
if @tasks.has_key? task
|
49
|
+
# hand it off to comp_tree
|
50
|
+
compute(task, threads)
|
32
51
|
end
|
33
|
-
|
52
|
+
ensure
|
53
|
+
@mutex.unlock
|
54
|
+
end
|
55
|
+
else
|
56
|
+
raise InvokeInsideInvoke
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Build and run the computation tree.
|
62
|
+
#
|
63
|
+
# Called from Parallel::Driver#invoke.
|
64
|
+
#
|
65
|
+
def compute(root_task, threads)
|
66
|
+
CompTree.build do |driver|
|
67
|
+
# keep this around for optimization
|
68
|
+
needed_prereq_names = Array.new
|
69
|
+
|
70
|
+
@tasks.each_pair do |task, (task_args, prereqs)|
|
71
|
+
# if a prereq is not needed then it didn't get into @tasks
|
72
|
+
needed_prereq_names.clear
|
73
|
+
prereqs.each do |prereq|
|
74
|
+
needed_prereq_names << prereq.name if @tasks.has_key? prereq
|
75
|
+
end
|
76
|
+
|
77
|
+
# define a computation node which executes the task
|
78
|
+
driver.define(task.name, *needed_prereq_names) {
|
79
|
+
task.execute(task_args)
|
80
|
+
}
|
81
|
+
end
|
34
82
|
|
35
|
-
#
|
36
|
-
|
37
|
-
#
|
38
|
-
driver.compute(root_node.name, self.num_threads)
|
83
|
+
# punch it
|
84
|
+
driver.compute(root_task.name, threads)
|
39
85
|
end
|
40
86
|
end
|
41
87
|
end
|
88
|
+
|
89
|
+
module ApplicationMixin
|
90
|
+
def parallel
|
91
|
+
@parallel ||= Driver.new
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module TaskMixin
|
96
|
+
#
|
97
|
+
# Top-level parallel invocation.
|
98
|
+
#
|
99
|
+
# Called from Task#invoke.
|
100
|
+
#
|
101
|
+
def invoke_parallel(*task_args)
|
102
|
+
application.parallel.invoke(application.options.threads, self, *task_args)
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Collect tasks for parallel execution.
|
107
|
+
#
|
108
|
+
# Called from Task#invoke_with_call_chain.
|
109
|
+
#
|
110
|
+
def invoke_with_call_chain_collector(task_args, new_chain, previous_chain)
|
111
|
+
prereqs = invoke_prerequisites_collector(task_args, new_chain)
|
112
|
+
parallel = application.parallel
|
113
|
+
if needed? or parallel.tasks[self]
|
114
|
+
parallel.tasks[self] = [task_args, prereqs]
|
115
|
+
unless previous_chain == InvocationChain::EMPTY
|
116
|
+
#
|
117
|
+
# Touch the parent to propagate 'needed?' upwards. This
|
118
|
+
# works because the recursion is depth-first.
|
119
|
+
#
|
120
|
+
parallel.tasks[previous_chain.value] = true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Dry-run invoke prereqs and return the prereq instances.
|
127
|
+
# This also serves to avoid MultiTask#invoke_prerequisites.
|
128
|
+
#
|
129
|
+
# Called from Task#invoke_with_call_chain_collector.
|
130
|
+
#
|
131
|
+
def invoke_prerequisites_collector(task_args, invocation_chain)
|
132
|
+
@prerequisites.map { |n|
|
133
|
+
invoke_prerequisite(n, task_args, invocation_chain)
|
134
|
+
}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Error indicating Task#invoke was called inside Task#invoke
|
141
|
+
# during parallel execution.
|
142
|
+
#
|
143
|
+
class InvokeInsideInvoke < StandardError
|
144
|
+
def message
|
145
|
+
"Cannot call Task#invoke within a task during parallel execution."
|
146
|
+
end
|
42
147
|
end
|
43
148
|
end
|
data/test/filecreation.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
module FileCreation
|
4
|
+
ANCIENT_FILE = 'testdata/anc'
|
4
5
|
OLDFILE = "testdata/old"
|
6
|
+
MIDDLE_AGED_FILE = "testdata/mid"
|
5
7
|
NEWFILE = "testdata/new"
|
6
8
|
|
7
9
|
def create_timed_files(oldfile, *newfiles)
|
@@ -15,6 +17,16 @@ module FileCreation
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
20
|
+
def create_dispersed_timed_files(*files)
|
21
|
+
create_file(files.first)
|
22
|
+
(1...files.size).each do |index|
|
23
|
+
while create_file(files[index]) <= File.stat(files[index - 1]).mtime
|
24
|
+
sleep(0.1)
|
25
|
+
File.delete(files[index])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
def create_dir(dirname)
|
19
31
|
FileUtils.mkdir_p(dirname) unless File.exist?(dirname)
|
20
32
|
File.stat(dirname).mtime
|
data/test/rake_test_setup.rb
CHANGED
@@ -7,6 +7,7 @@ rescue LoadError
|
|
7
7
|
# got no gems
|
8
8
|
end
|
9
9
|
|
10
|
+
require 'thread'
|
10
11
|
require 'flexmock/test_unit'
|
11
12
|
|
12
13
|
if RUBY_VERSION >= "1.9.0"
|
@@ -22,3 +23,23 @@ module TestMethods
|
|
22
23
|
assert_raise(ex, msg, &block)
|
23
24
|
end
|
24
25
|
end
|
26
|
+
|
27
|
+
class SerializedArray
|
28
|
+
def initialize
|
29
|
+
@mutex = Mutex.new
|
30
|
+
@array = Array.new
|
31
|
+
end
|
32
|
+
|
33
|
+
Array.public_instance_methods.each do |method_name|
|
34
|
+
unless method_name =~ %r!\A__! or method_name =~ %r!\A(object_)?id\Z!
|
35
|
+
# TODO: jettison 1.8.6; use define_method with |&block|
|
36
|
+
eval %{
|
37
|
+
def #{method_name}(*args, &block)
|
38
|
+
@mutex.synchronize {
|
39
|
+
@array.send('#{method_name}', *args, &block)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/test/test_application.rb
CHANGED
@@ -22,6 +22,7 @@ class TestApplication < Test::Unit::TestCase
|
|
22
22
|
|
23
23
|
def setup
|
24
24
|
@app = Rake::Application.new
|
25
|
+
@app.options.threads = Rake.application.options.threads
|
25
26
|
@app.options.rakelib = []
|
26
27
|
end
|
27
28
|
|
@@ -570,6 +571,7 @@ class TestApplicationOptions < Test::Unit::TestCase
|
|
570
571
|
def command_line(*options)
|
571
572
|
options.each do |opt| ARGV << opt end
|
572
573
|
@app = Rake::Application.new
|
574
|
+
@app.options.threads = Rake.application.options.threads
|
573
575
|
def @app.exit(*args)
|
574
576
|
throw :system_exit, :exit
|
575
577
|
end
|
@@ -585,6 +587,7 @@ end
|
|
585
587
|
class TestTaskArgumentParsing < Test::Unit::TestCase
|
586
588
|
def setup
|
587
589
|
@app = Rake::Application.new
|
590
|
+
@app.options.threads = Rake.application.options.threads
|
588
591
|
end
|
589
592
|
|
590
593
|
def test_name_only
|
@@ -630,6 +633,7 @@ class TestTaskArgumentParsing < Test::Unit::TestCase
|
|
630
633
|
|
631
634
|
def test_terminal_width_using_env
|
632
635
|
app = Rake::Application.new
|
636
|
+
app.options.threads = Rake.application.options.threads
|
633
637
|
in_environment('RAKE_COLUMNS' => '1234') do
|
634
638
|
assert_equal 1234, app.terminal_width
|
635
639
|
end
|
@@ -637,6 +641,7 @@ class TestTaskArgumentParsing < Test::Unit::TestCase
|
|
637
641
|
|
638
642
|
def test_terminal_width_using_stty
|
639
643
|
app = Rake::Application.new
|
644
|
+
app.options.threads = Rake.application.options.threads
|
640
645
|
flexmock(app,
|
641
646
|
:unix? => true,
|
642
647
|
:dynamic_width_stty => 1235,
|
@@ -648,6 +653,7 @@ class TestTaskArgumentParsing < Test::Unit::TestCase
|
|
648
653
|
|
649
654
|
def test_terminal_width_using_tput
|
650
655
|
app = Rake::Application.new
|
656
|
+
app.options.threads = Rake.application.options.threads
|
651
657
|
flexmock(app,
|
652
658
|
:unix? => true,
|
653
659
|
:dynamic_width_stty => 0,
|
@@ -659,6 +665,7 @@ class TestTaskArgumentParsing < Test::Unit::TestCase
|
|
659
665
|
|
660
666
|
def test_terminal_width_using_hardcoded_80
|
661
667
|
app = Rake::Application.new
|
668
|
+
app.options.threads = Rake.application.options.threads
|
662
669
|
flexmock(app, :unix? => false)
|
663
670
|
in_environment('RAKE_COLUMNS' => nil) do
|
664
671
|
assert_equal 80, app.terminal_width
|
@@ -667,6 +674,7 @@ class TestTaskArgumentParsing < Test::Unit::TestCase
|
|
667
674
|
|
668
675
|
def test_terminal_width_with_failure
|
669
676
|
app = Rake::Application.new
|
677
|
+
app.options.threads = Rake.application.options.threads
|
670
678
|
flexmock(app).should_receive(:unix?).and_throw(RuntimeError)
|
671
679
|
in_environment('RAKE_COLUMNS' => nil) do
|
672
680
|
assert_equal 80, app.terminal_width
|
data/test/test_definitions.rb
CHANGED
@@ -48,14 +48,18 @@ class TestDefinitions < Test::Unit::TestCase
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def test_incremental_definitions
|
51
|
-
runs =
|
51
|
+
runs = SerializedArray.new
|
52
52
|
task :t1 => [:t2] do runs << "A"; 4321 end
|
53
53
|
task :t1 => [:t3] do runs << "B"; 1234 end
|
54
54
|
task :t1 => [:t3]
|
55
55
|
task :t2
|
56
56
|
task :t3
|
57
57
|
Task[:t1].invoke
|
58
|
-
|
58
|
+
if Rake.application.options.threads == 1
|
59
|
+
assert_equal ["A", "B"], runs
|
60
|
+
else
|
61
|
+
assert_equal ["A", "B"], runs.sort
|
62
|
+
end
|
59
63
|
assert_equal ["t2", "t3"], Task[:t1].prerequisites
|
60
64
|
end
|
61
65
|
|
data/test/test_file_task.rb
CHANGED
@@ -12,11 +12,17 @@ class TestFileTask < Test::Unit::TestCase
|
|
12
12
|
include FileCreation
|
13
13
|
include TestMethods
|
14
14
|
|
15
|
+
FILES = [ANCIENT_FILE, OLDFILE, MIDDLE_AGED_FILE, NEWFILE]
|
16
|
+
|
15
17
|
def setup
|
16
18
|
Task.clear
|
17
|
-
@runs =
|
18
|
-
FileUtils.rm_f
|
19
|
-
|
19
|
+
@runs = SerializedArray.new
|
20
|
+
FileUtils.rm_f FILES
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_create_dispersed_timed_files
|
24
|
+
create_dispersed_timed_files(*FILES)
|
25
|
+
assert_equal FILES, FILES.sort_by { |f| File.stat(f).mtime }
|
20
26
|
end
|
21
27
|
|
22
28
|
def test_file_need
|
@@ -73,6 +79,46 @@ class TestFileTask < Test::Unit::TestCase
|
|
73
79
|
assert_nothing_raised do Task[OLDFILE].invoke end
|
74
80
|
end
|
75
81
|
|
82
|
+
def test_old_file_in_between
|
83
|
+
create_dispersed_timed_files(*FILES)
|
84
|
+
|
85
|
+
file MIDDLE_AGED_FILE => OLDFILE do |t|
|
86
|
+
@runs << t.name
|
87
|
+
end
|
88
|
+
file OLDFILE => NEWFILE do |t|
|
89
|
+
@runs << t.name
|
90
|
+
touch OLDFILE, :verbose => false
|
91
|
+
end
|
92
|
+
file NEWFILE do |t|
|
93
|
+
@runs << t.name
|
94
|
+
end
|
95
|
+
|
96
|
+
Task[MIDDLE_AGED_FILE].invoke
|
97
|
+
assert_equal([OLDFILE, MIDDLE_AGED_FILE], @runs)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_two_old_files_in_between
|
101
|
+
create_dispersed_timed_files(*FILES)
|
102
|
+
|
103
|
+
file MIDDLE_AGED_FILE => OLDFILE do |t|
|
104
|
+
@runs << t.name
|
105
|
+
end
|
106
|
+
file OLDFILE => ANCIENT_FILE do |t|
|
107
|
+
@runs << t.name
|
108
|
+
touch OLDFILE, :verbose => false
|
109
|
+
end
|
110
|
+
file ANCIENT_FILE => NEWFILE do |t|
|
111
|
+
@runs << t.name
|
112
|
+
touch ANCIENT_FILE, :verbose => false
|
113
|
+
end
|
114
|
+
file NEWFILE do |t|
|
115
|
+
@runs << t.name
|
116
|
+
end
|
117
|
+
|
118
|
+
Task[MIDDLE_AGED_FILE].invoke
|
119
|
+
assert_equal([ANCIENT_FILE, OLDFILE, MIDDLE_AGED_FILE], @runs)
|
120
|
+
end
|
121
|
+
|
76
122
|
# I have currently disabled this test. I'm not convinced that
|
77
123
|
# deleting the file target on failure is always the proper thing to
|
78
124
|
# do. I'm willing to hear input on this topic.
|
data/test/test_multitask.rb
CHANGED
data/test/test_parallel.rb
CHANGED
@@ -1,56 +1,195 @@
|
|
1
1
|
|
2
2
|
require 'rbconfig'
|
3
3
|
require 'test/unit'
|
4
|
+
require 'rake'
|
5
|
+
require 'test/rake_test_setup'
|
4
6
|
|
5
|
-
|
7
|
+
if Rake.application.options.threads > 1
|
8
|
+
class TestParallel < Test::Unit::TestCase
|
9
|
+
VISUALS = false
|
10
|
+
TIME_STEP = 0.25
|
11
|
+
TIME_EPSILON = 0.05
|
12
|
+
MAX_THREADS = 5
|
6
13
|
|
14
|
+
def trace(str)
|
15
|
+
if VISUALS
|
16
|
+
puts str
|
17
|
+
end
|
18
|
+
end
|
7
19
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
/ \
|
12
|
-
/ \
|
13
|
-
a b
|
14
|
-
/ \
|
15
|
-
/ \
|
16
|
-
x y
|
20
|
+
def assert_order(expected, actual)
|
21
|
+
assert_in_delta(expected*TIME_STEP, actual, TIME_EPSILON)
|
22
|
+
end
|
17
23
|
|
18
|
-
|
19
|
-
|
20
|
-
if Rake.application.num_threads > 1
|
21
|
-
class TestSimpleParallel < Test::Unit::TestCase
|
22
|
-
def setup
|
23
|
-
puts PARALLEL_TEST_MESSAGE
|
24
|
+
def teardown
|
25
|
+
Rake::Task.clear
|
24
26
|
end
|
25
27
|
|
26
28
|
def test_parallel
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
trace GRAPH
|
30
|
+
|
31
|
+
data = Hash.new { |hash, key| hash[key] = Hash.new }
|
32
|
+
|
33
|
+
(1..MAX_THREADS).each { |threads|
|
34
|
+
app = Rake::Application.new
|
35
|
+
app.options.threads = threads
|
36
|
+
|
37
|
+
app.define_task Rake::Task, :default => [:a, :b]
|
38
|
+
app.define_task Rake::Task, :a => [:x, :y]
|
39
|
+
app.define_task Rake::Task, :b
|
40
|
+
|
41
|
+
mutex = Mutex.new
|
42
|
+
STDOUT.sync = true
|
43
|
+
start_time = nil
|
44
|
+
|
45
|
+
%w[default a b x y].each { |task_name|
|
46
|
+
app.define_task Rake::Task, task_name.to_sym do
|
47
|
+
mutex.synchronize {
|
48
|
+
trace "task #{task_name}"
|
49
|
+
data[threads][task_name] = Time.now - start_time
|
50
|
+
}
|
51
|
+
sleep(TIME_STEP)
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
trace "-"*50
|
56
|
+
trace "threads: #{threads}"
|
57
|
+
start_time = Time.now
|
58
|
+
app[:default].invoke
|
59
|
+
}
|
60
|
+
|
61
|
+
assert_order(0, data[1]["x"])
|
62
|
+
assert_order(1, data[1]["y"])
|
63
|
+
assert_order(2, data[1]["a"])
|
64
|
+
assert_order(3, data[1]["b"])
|
65
|
+
assert_order(4, data[1]["default"])
|
66
|
+
|
67
|
+
assert_order(0, data[2]["x"])
|
68
|
+
assert_order(0, data[2]["y"])
|
69
|
+
assert_order(1, data[2]["a"])
|
70
|
+
assert_order(1, data[2]["b"])
|
71
|
+
assert_order(2, data[2]["default"])
|
72
|
+
|
73
|
+
(3..MAX_THREADS).each { |threads|
|
74
|
+
assert_order(0, data[threads]["x"])
|
75
|
+
assert_order(0, data[threads]["y"])
|
76
|
+
assert_order(0, data[threads]["b"])
|
77
|
+
assert_order(1, data[threads]["a"])
|
78
|
+
assert_order(2, data[threads]["default"])
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_invoke_inside_invoke
|
83
|
+
assert_raises(Rake::InvokeInsideInvoke) {
|
84
|
+
app = Rake::Application.new
|
85
|
+
app.options.threads = 4
|
86
|
+
app.define_task Rake::Task, :root do
|
87
|
+
app[:stuff].invoke
|
88
|
+
end
|
89
|
+
app.define_task Rake::Task, :stuff do
|
90
|
+
flunk
|
91
|
+
end
|
92
|
+
app[:root].invoke
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_randomize
|
97
|
+
size = 100
|
98
|
+
[false, true].each do |randomize|
|
99
|
+
memo = SerializedArray.new
|
100
|
+
app = Rake::Application.new
|
101
|
+
app.define_task Rake::Task, :root
|
102
|
+
size.times { |n|
|
103
|
+
app.define_task Rake::Task, :root => n.to_s
|
104
|
+
app.define_task Rake::Task, n.to_s do
|
105
|
+
memo << n
|
106
|
+
end
|
107
|
+
}
|
108
|
+
app.options.randomize = randomize
|
109
|
+
app[:root].invoke
|
110
|
+
numbers = (0...size).to_a
|
111
|
+
if randomize
|
112
|
+
assert_not_equal(numbers, memo)
|
36
113
|
else
|
37
|
-
|
114
|
+
assert_equal(numbers, memo)
|
38
115
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_multitask_not_called
|
120
|
+
# ensure MultiTask methods are not called by hijacking all of them
|
121
|
+
|
122
|
+
originals = [
|
123
|
+
:private_instance_methods,
|
124
|
+
:protected_instance_methods,
|
125
|
+
:public_instance_methods,
|
126
|
+
].inject(Hash.new) { |acc, query|
|
127
|
+
result = Rake::MultiTask.send(query, false).inject(Hash.new) {
|
128
|
+
|sub_acc, method_name|
|
129
|
+
sub_acc.merge!(
|
130
|
+
method_name => Rake::MultiTask.instance_method(method_name)
|
131
|
+
)
|
132
|
+
}
|
133
|
+
acc.merge!(result)
|
134
|
+
}
|
135
|
+
|
136
|
+
memo = SerializedArray.new
|
137
|
+
|
138
|
+
Rake::MultiTask.module_eval {
|
139
|
+
originals.each_pair { |method_name, method_object|
|
140
|
+
remove_method method_name
|
141
|
+
define_method method_name do |*args|
|
142
|
+
# missing |&block| due to 1.8.6, but not using it anyway
|
143
|
+
memo << 'called'
|
144
|
+
method_object.bind(self).call(*args)
|
50
145
|
end
|
51
|
-
assert(ruby(*args))
|
52
146
|
}
|
53
147
|
}
|
148
|
+
|
149
|
+
begin
|
150
|
+
app = Rake::Application.new
|
151
|
+
|
152
|
+
define = lambda {
|
153
|
+
app.define_task Rake::Task, task(:x) { }
|
154
|
+
app.define_task Rake::Task, task(:y) { }
|
155
|
+
app.define_task Rake::MultiTask, :root => [:x, :y]
|
156
|
+
}
|
157
|
+
|
158
|
+
app.options.threads = 1
|
159
|
+
define.call
|
160
|
+
app[:root].invoke
|
161
|
+
assert_equal ['called'], memo
|
162
|
+
|
163
|
+
app.clear
|
164
|
+
memo.clear
|
165
|
+
assert_raises(RuntimeError) { app[:root].invoke }
|
166
|
+
|
167
|
+
app.options.threads = 4
|
168
|
+
define.call
|
169
|
+
app[:root].invoke
|
170
|
+
assert_equal [], memo
|
171
|
+
ensure
|
172
|
+
Rake::MultiTask.module_eval {
|
173
|
+
originals.each_pair { |method_name, method_object|
|
174
|
+
remove_method method_name
|
175
|
+
define_method method_name, method_object
|
176
|
+
}
|
177
|
+
}
|
178
|
+
end
|
54
179
|
end
|
180
|
+
|
181
|
+
GRAPH = <<-'EOS'
|
182
|
+
|
183
|
+
Task graph for sample parallel execution:
|
184
|
+
|
185
|
+
default
|
186
|
+
/ \
|
187
|
+
/ \
|
188
|
+
a b
|
189
|
+
/ \
|
190
|
+
/ \
|
191
|
+
x y
|
192
|
+
|
193
|
+
EOS
|
55
194
|
end
|
56
195
|
end
|