build-graph 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2660012c78eab9ed19ce99d5ba59be689e5a25f86c3dc376d8ea11ac86290275
4
- data.tar.gz: bbae16e2bfaa322b26e521a319ce2431d636e263ee42685706c893e02011eebf
3
+ metadata.gz: b6ca9917fd8c609a63c0608a797010e9df257a64709c0129c91a49847759cc35
4
+ data.tar.gz: e1f7c0aa6231782ad4767a982c657323f128455200dd324a1ce4149fdf912563
5
5
  SHA512:
6
- metadata.gz: da7be812b70a047264c5950de7ee69d4b82d5ec06ec2fd5ef636b68ecad77dcb8271ab82dfe4f749571db5c11dfe00295ebf235a15605a4483db103d8bae1f3d
7
- data.tar.gz: 1e0fe54dde3ac23a375884e8bc1a8abb0635d7bcf0a8e21864e6dde9d2a144549612f119cfd6e089f2a6c8a2e51fe7bd76f6c1db753b00a8fffb3602c5d0c259
6
+ metadata.gz: 75a5812081eda17786f4a8c3abbda92b187f4b57ccbd130c6a76a8ea034836f22e5bb42d15f8e6634d4e1a5449953eaa144cc69add30889658b97bd7c0b45958
7
+ data.tar.gz: ccdee7f9215f75f6489455bad14ab71644030a622047fedafcaf55c76c712cb587ef5c44aee322f51b1c49703aef22be93083f1caf66d874cb2f794c6ede5e04
@@ -89,7 +89,7 @@ module Build
89
89
  end
90
90
 
91
91
  def inspect
92
- "#<#{self.class} #{@inputs.inspect} => #{@outputs.inspect} by #{@process.inspect}>"
92
+ "#<#{self.class} #{@inputs.inspect} => #{@outputs.inspect} by #{@process}>"
93
93
  end
94
94
 
95
95
  def self.top(inputs = Files::Paths::NONE, outputs = :inherit, **options, &block)
@@ -42,12 +42,14 @@ module Build
42
42
  @walker.tasks[node] = self
43
43
 
44
44
  @node = node
45
+ @fiber = nil
45
46
 
46
47
  @error = nil
47
48
 
48
49
  @children = []
49
50
 
50
51
  @state = nil
52
+ @annotation = nil
51
53
 
52
54
  @inputs_failed = false
53
55
  end
@@ -58,6 +60,8 @@ module Build
58
60
  attr :children
59
61
  attr :state
60
62
 
63
+ attr :annotation
64
+
61
65
  # The error, if the execution of the node fails.
62
66
  attr :error
63
67
 
@@ -75,6 +79,10 @@ module Build
75
79
  # Inforn the walker a new task is being generated for this node:
76
80
  @walker.enter(self)
77
81
 
82
+ if @fiber
83
+ raise RuntimeError, "Task is already running!"
84
+ end
85
+
78
86
  @fiber = Fiber.new do
79
87
  # If all inputs were good, we can update the node.
80
88
  if wait_for_inputs?
@@ -96,6 +104,8 @@ module Build
96
104
  @state ||= :complete
97
105
 
98
106
  @walker.exit(self)
107
+
108
+ @fiber = nil
99
109
  end
100
110
 
101
111
  # Schedule the work, hopefully synchronously:
@@ -124,7 +134,11 @@ module Build
124
134
  # Returns true if the outputs of the task are out of date w.r.t. the inputs.
125
135
  # Currently, does not take into account if the input is a glob and files have been added.
126
136
  def dirty?
127
- @outputs.dirty?(@inputs)
137
+ if @outputs
138
+ @outputs.dirty?(@inputs)
139
+ else
140
+ true
141
+ end
128
142
  end
129
143
 
130
144
  def changed!
@@ -157,12 +171,15 @@ module Build
157
171
  if @node.inherit_outputs?
158
172
  @outputs = Files::State.new(self.children_outputs)
159
173
  else
174
+ @annotation = "update outputs"
160
175
  # After the task has finished, we update the output states:
161
176
  @outputs.update!
162
177
  end
163
178
  end
164
179
 
165
180
  def fail!(error)
181
+ @annotation = "failed"
182
+
166
183
  if logger = @walker.logger
167
184
  logger.error("Task #{self} failed: #{error}")
168
185
  logger.debug(error) if error.kind_of?(Exception)
@@ -174,12 +191,15 @@ module Build
174
191
 
175
192
  # Returns false if any input failed.
176
193
  def wait_for_inputs?
194
+ @annotation = "wait for inputs"
177
195
  # Wait on any inputs, returns whether any inputs failed:
178
196
  @walker.wait_on_paths(self, @inputs)
179
197
  end
180
198
 
181
199
  # Returns false if any child failed.
182
200
  def wait_for_children?
201
+ @annotation = "wait for children"
202
+
183
203
  @walker.wait_for_children(self, @children)
184
204
  end
185
205
  end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Build
22
22
  module Graph
23
- VERSION = "1.2.0"
23
+ VERSION = "1.2.1"
24
24
  end
25
25
  end
@@ -86,7 +86,7 @@ module Build
86
86
  def call(node, parent_task = nil)
87
87
  # We try to fetch the task if it has already been invoked, otherwise we create a new task.
88
88
  @tasks.fetch(node) do
89
- @logger.debug{"Update: #{node} #{parent_task.inspect}"}
89
+ @logger.debug{"Update: #{node} #{parent_task.class}"}
90
90
 
91
91
  # This method should add the node
92
92
  @update.call(self, node, parent_task)
@@ -108,23 +108,25 @@ module Build
108
108
  edge = Edge.new
109
109
 
110
110
  paths = paths.collect(&:to_s)
111
- @logger.debug{"Task #{task} is waiting on paths #{paths}"}
112
111
 
113
112
  paths.each do |path|
114
113
  # Is there a task generating this output?
115
114
  if outputs = @outputs[path]
115
+ @logger.debug{"Task #{task} is waiting on path #{path}"}
116
+
116
117
  # When the output is ready, trigger this edge:
117
118
  outputs << edge
118
119
  edge.increment!
120
+ elsif !File.exist?(path)
121
+ raise RuntimeError, "File #{path} is not being generated by any active task!"
122
+ # What should we do about paths which haven't been registered as outputs?
123
+ # Either they exist - or they don't.
124
+ # If they exist, it means they are probably static inputs of the build graph.
125
+ # If they don't, it might be an error, or it might be deliberate.
119
126
  end
120
-
121
- # What should we do about paths which haven't been registered as outputs?
122
- # Either they exist - or they don't.
123
- # If they exist, it means they are probably static inputs of the build graph.
124
- # If they don't, it might be an error, or it might be deliberate.
125
127
  end
126
128
 
127
- failed = paths.any?{|path| @failed_outputs.include? path}
129
+ failed = paths.any?{|path| @failed_outputs.include?(path)}
128
130
 
129
131
  return edge.wait && !failed
130
132
  end
@@ -167,7 +169,8 @@ module Build
167
169
  @logger.debug{"Task will generate outputs: #{outputs.to_a.collect(&:to_s).inspect}"}
168
170
 
169
171
  outputs.each do |path|
170
- @outputs[path.to_s] = []
172
+ # Tasks which have children tasks may list the same output twice. This is not a bug.
173
+ @outputs[path.to_s] ||= []
171
174
  end
172
175
  end
173
176
  end
@@ -197,6 +200,7 @@ module Build
197
200
  end
198
201
 
199
202
  if edges = @outputs.delete(path)
203
+ # @logger.debug "\tUpdating #{edges.count} edges..."
200
204
  edges.each{|edge| edge.traverse(task)}
201
205
  end
202
206
  end
@@ -21,158 +21,154 @@
21
21
 
22
22
  require_relative 'process_graph'
23
23
 
24
- module Build::Graph::GraphSpec
25
- include ProcessGraph
24
+ RSpec.describe Build::Graph do
25
+ let(:group) {Process::Group.new}
26
26
 
27
- RSpec.describe Build::Graph do
28
- let(:group) {Process::Group.new}
27
+ let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
28
+
29
+ it "shouldn't update mtime" do
30
+ test_glob = Build::Files::Glob.new(__dir__, "*.rb")
31
+ listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
29
32
 
30
- let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
33
+ FileUtils.rm_f listing_output.to_a
31
34
 
32
- it "shouldn't update mtime" do
33
- test_glob = Glob.new(__dir__, "*.rb")
34
- listing_output = Paths.directory(__dir__, ["listing.txt"])
35
-
36
- FileUtils.rm_f listing_output.to_a
37
-
38
- walker = Walker.for(ProcessTask, group)
39
-
40
- top = ProcessNode.top do
41
- process test_glob, listing_output do
42
- run("ls", "-la", *inputs, :out => outputs.first.for_writing)
43
- end
44
- end
45
-
46
- group.wait do
47
- walker.update(top)
48
- end
49
-
50
- first_modified_time = listing_output.first.modified_time
51
-
52
- group.wait do
53
- walker.update(top)
54
- end
55
-
56
- # The output file shouldn't have been changed because already exists and the input files haven't changed either:
57
- second_modified_time = listing_output.first.modified_time
58
-
59
- # The granularity of mtime on some systems is a bit weird:
60
- expect(second_modified_time.to_f).to be_within(0.001).of(first_modified_time.to_f)
61
-
62
- FileUtils.rm_f listing_output.to_a
63
- walker.monitor.update(listing_output.roots)
64
-
65
- # The granularity of modification times isn't that great, so we use >= below.
66
- # sleep 1
67
-
68
- group.wait do
69
- walker.update(top)
35
+ walker = Build::Graph::Walker.for(ProcessTask, group)
36
+
37
+ top = ProcessNode.top do
38
+ process test_glob, listing_output do
39
+ run("ls", "-la", *inputs, :out => outputs.first.for_writing)
70
40
  end
71
-
72
- expect(listing_output.first.modified_time).to be >= first_modified_time
73
-
74
- FileUtils.rm_f listing_output.to_a
75
41
  end
76
42
 
77
- it "should compile program and respond to changes in source code" do
78
- program_root = Path.join(__dir__, "program")
79
- code_glob = Glob.new(program_root, "*.cpp")
80
- program_path = Path.join(program_root, "dictionary-sort")
81
-
82
- walker = Walker.for(ProcessTask, group)
83
-
84
- #FileUtils.touch(code_glob.first)
85
-
86
- top = ProcessNode.top do
87
- process code_glob, program_path do
88
- object_files = inputs.with(extension: ".o") do |input_path, output_path|
89
- depfile_path = input_path + ".d"
90
-
91
- dependencies = Paths.new(input_path)
92
-
93
- if File.exist? depfile_path
94
- depfile = Build::Makefile.load_file(depfile_path)
95
-
96
- dependencies = depfile[output_path] || dependencies
97
- end
43
+ group.wait do
44
+ walker.update(top)
45
+ end
46
+
47
+ first_modified_time = listing_output.first.modified_time
48
+
49
+ group.wait do
50
+ walker.update(top)
51
+ end
52
+
53
+ # The output file shouldn't have been changed because already exists and the input files haven't changed either:
54
+ second_modified_time = listing_output.first.modified_time
55
+
56
+ # The granularity of mtime on some systems is a bit weird:
57
+ expect(second_modified_time.to_f).to be_within(0.001).of(first_modified_time.to_f)
58
+
59
+ FileUtils.rm_f listing_output.to_a
60
+ walker.monitor.update(listing_output.roots)
61
+
62
+ # The granularity of modification times isn't that great, so we use >= below.
63
+ # sleep 1
64
+
65
+ group.wait do
66
+ walker.update(top)
67
+ end
68
+
69
+ expect(listing_output.first.modified_time).to be >= first_modified_time
70
+
71
+ FileUtils.rm_f listing_output.to_a
72
+ end
73
+
74
+ it "should compile program and respond to changes in source code" do
75
+ program_root = Build::Files::Path.join(__dir__, "program")
76
+ code_glob = Build::Files::Glob.new(program_root, "*.cpp")
77
+ program_path = Build::Files::Path.join(program_root, "dictionary-sort")
78
+
79
+ walker = Build::Graph::Walker.for(ProcessTask, group)
80
+
81
+ #FileUtils.touch(code_glob.first)
82
+
83
+ top = ProcessNode.top do
84
+ process code_glob, program_path do
85
+ object_files = inputs.with(extension: ".o") do |input_path, output_path|
86
+ depfile_path = input_path + ".d"
87
+
88
+ dependencies = Build::Files::Paths.new(input_path)
89
+
90
+ if File.exist? depfile_path
91
+ depfile = Build::Makefile.load_file(depfile_path)
98
92
 
99
- process dependencies, output_path do
100
- run("clang++", "-MMD", "-O3",
101
- "-o", output_path.shortest_path(input_path.root),
102
- "-c", input_path.relative_path, "-std=c++11",
103
- chdir: input_path.root
104
- )
105
- end
93
+ dependencies = depfile[output_path] || dependencies
106
94
  end
107
95
 
108
- process object_files, program_path do
109
- run("clang++", "-O3", "-o", program_path, *object_files.to_a, "-lm", "-pthread")
96
+ process dependencies, output_path do
97
+ run("clang++", "-MMD", "-O3",
98
+ "-o", output_path.shortest_path(input_path.root),
99
+ "-c", input_path.relative_path, "-std=c++11",
100
+ chdir: input_path.root
101
+ )
110
102
  end
111
103
  end
112
104
 
113
- process program_path do
114
- run("./" + program_path.relative_path, chdir: program_path.root)
105
+ process object_files, program_path do
106
+ run("clang++", "-O3", "-o", program_path, *object_files.to_a, "-lm", "-pthread")
115
107
  end
116
108
  end
117
109
 
118
- group.wait do
119
- walker.update(top)
110
+ process program_path do
111
+ run("./" + program_path.relative_path, chdir: program_path.root)
120
112
  end
121
-
122
- expect(program_path).to be_exist
123
- expect(code_glob.first.modified_time).to be <= program_path.modified_time
124
113
  end
125
114
 
126
- it "should copy files incrementally" do
127
- program_root = Path.join(__dir__, "program")
128
- files = Glob.new(program_root, "*.cpp")
129
- destination = Path.new(__dir__) + "tmp"
130
-
131
- walker = Walker.for(ProcessTask, group)
115
+ group.wait do
116
+ walker.update(top)
117
+ end
118
+
119
+ expect(program_path).to be_exist
120
+ expect(code_glob.first.modified_time).to be <= program_path.modified_time
121
+ end
122
+
123
+ it "should copy files incrementally" do
124
+ program_root = Build::Files::Path.join(__dir__, "program")
125
+ files = Build::Files::Glob.new(program_root, "*.cpp")
126
+ destination = Build::Files::Path.new(__dir__) + "tmp"
127
+
128
+ walker = Build::Graph::Walker.for(ProcessTask, group)
129
+
130
+ top = ProcessNode.top files do
131
+ mkpath destination
132
132
 
133
- top = ProcessNode.top files do
134
- mkpath destination
133
+ inputs.each do |source_path|
134
+ destination_path = source_path.rebase(destination)
135
135
 
136
- inputs.each do |source_path|
137
- destination_path = source_path.rebase(destination)
138
-
139
- process source_path, destination_path do
140
- $stderr.puts "Copying #{inputs.first} -> #{outputs.first}"
141
- install inputs.first, outputs.first
142
- end
136
+ process source_path, destination_path do
137
+ $stderr.puts "Copying #{inputs.first} -> #{outputs.first}"
138
+ install inputs.first, outputs.first
143
139
  end
144
140
  end
141
+ end
142
+
143
+ mutex = Mutex.new
144
+ files_deleted = false
145
+
146
+ thread = Thread.new do
147
+ sleep 1
145
148
 
146
- mutex = Mutex.new
147
- files_deleted = false
148
-
149
- thread = Thread.new do
150
- sleep 1
149
+ mutex.synchronize do
150
+ destination.glob("*.cpp").delete
151
151
 
152
- mutex.synchronize do
153
- destination.glob("*.cpp").delete
154
-
155
- files_deleted = true
156
- end
152
+ files_deleted = true
157
153
  end
158
-
159
- walker.run do
160
- mutex.synchronize do
161
- group.wait do
162
- walker.update(top)
163
- end
154
+ end
155
+
156
+ walker.run do
157
+ mutex.synchronize do
158
+ group.wait do
159
+ walker.update(top)
164
160
  end
165
-
166
- break if files_deleted
167
161
  end
168
162
 
169
- thread.join
170
-
171
- expect(destination).to be_exist
172
- # This line failed, may still be a race condition:
173
- expect(destination.glob("*.cpp").count).to be == 2
174
-
175
- destination.delete
163
+ break if files_deleted
176
164
  end
165
+
166
+ thread.join
167
+
168
+ expect(destination).to be_exist
169
+ # This line failed, may still be a race condition:
170
+ expect(destination.glob("*.cpp").count).to be == 2
171
+
172
+ destination.delete
177
173
  end
178
174
  end
@@ -23,34 +23,29 @@ require 'build/graph/walker'
23
23
  require 'build/graph/task'
24
24
  require 'build/files'
25
25
 
26
- module Build::Graph::InheritSpec
27
- include Build::Graph
28
- include Build::Files
29
-
30
- RSpec.describe Build::Graph::Walker do
31
- it "should inherit children outputs", :focus do
32
- test_glob = Glob.new(__dir__, "*.rb")
33
- listing_output = Paths.directory(__dir__, ["listing.txt"])
26
+ RSpec.describe Build::Graph::Walker do
27
+ it "should inherit children outputs", :focus do
28
+ test_glob = Build::Files::Glob.new(__dir__, "*.rb")
29
+ listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
30
+
31
+ node_a = Build::Graph::Node.new(Build::Files::Paths::NONE, :inherit, "a")
32
+ node_b = Build::Graph::Node.new(test_glob, listing_output, "b")
33
+
34
+ walker = Build::Graph::Walker.new do |walker, node|
35
+ task = Build::Graph::Task.new(walker, node)
34
36
 
35
- node_a = Node.new(Paths::NONE, :inherit, "a")
36
- node_b = Node.new(test_glob, listing_output, "b")
37
-
38
- walker = Walker.new do |walker, node|
39
- task = Task.new(walker, node)
40
-
41
- task.visit do
42
- if node.process == 'a'
43
- task.invoke(node_b)
44
- end
37
+ task.visit do
38
+ if node.process == 'a'
39
+ task.invoke(node_b)
45
40
  end
46
41
  end
47
-
48
- walker.update([node_a])
49
-
50
- task_a = walker.tasks[node_a]
51
- task_b = walker.tasks[node_b]
52
-
53
- expect(task_a.outputs.to_a).to be == task_b.outputs.to_a
54
42
  end
43
+
44
+ walker.update([node_a])
45
+
46
+ task_a = walker.tasks[node_a]
47
+ task_b = walker.tasks[node_b]
48
+
49
+ expect(task_a.outputs.to_a).to be == task_b.outputs.to_a
55
50
  end
56
51
  end
@@ -3,79 +3,74 @@ require 'process/group'
3
3
  require 'build/files'
4
4
  require 'build/graph'
5
5
 
6
- module ProcessGraph
7
- include Build::Graph
8
- include Build::Files
9
-
10
- class ProcessNode < Node
11
- def initialize(inputs, outputs, block, title: nil)
12
- super(inputs, outputs, block.source_location)
13
-
14
- if title
15
- @title = title
16
- else
17
- @title = self.process
18
- end
19
-
20
- @block = block
21
- end
6
+ class ProcessNode < Build::Graph::Node
7
+ def initialize(inputs, outputs, block, title: nil)
8
+ super(inputs, outputs, block.source_location)
22
9
 
23
- def evaluate(context)
24
- context.instance_eval(&@block)
10
+ if title
11
+ @title = title
12
+ else
13
+ @title = self.process
25
14
  end
26
15
 
27
- attr :title
16
+ @block = block
17
+ end
18
+
19
+ def evaluate(context)
20
+ context.instance_eval(&@block)
28
21
  end
22
+
23
+ attr :title
24
+ end
29
25
 
30
- class ProcessTask < Task
31
- def initialize(walker, node, group)
32
- super(walker, node)
33
-
34
- @group = group
35
- end
36
-
37
- def process(inputs, outputs = :inherit, **options, &block)
38
- inputs = Build::Files::List.coerce(inputs)
39
- outputs = Build::Files::List.coerce(outputs) unless outputs.kind_of? Symbol
40
-
41
- node = ProcessNode.new(inputs, outputs, block, **options)
42
-
43
- self.invoke(node)
44
- end
26
+ class ProcessTask < Build::Graph::Task
27
+ def initialize(walker, node, group)
28
+ super(walker, node)
45
29
 
46
- def wet?
47
- @node.dirty?
48
- end
30
+ @group = group
31
+ end
32
+
33
+ def process(inputs, outputs = :inherit, **options, &block)
34
+ inputs = Build::Files::List.coerce(inputs)
35
+ outputs = Build::Files::List.coerce(outputs) unless outputs.kind_of? Symbol
49
36
 
50
- def run(*arguments)
51
- if wet?
52
- puts "\t[run] #{arguments.join(' ')}"
53
- status = @group.spawn(*arguments)
54
-
55
- if status != 0
56
- raise CommandError.new(status)
57
- end
58
- end
59
- end
37
+ node = ProcessNode.new(inputs, outputs, block, **options)
60
38
 
61
- def mkpath(*args)
62
- return unless wet?
39
+ self.invoke(node)
40
+ end
41
+
42
+ def wet?
43
+ @node.dirty?
44
+ end
45
+
46
+ def run(*arguments)
47
+ if wet?
48
+ puts "\t[run] #{arguments.join(' ')}"
49
+ status = @group.spawn(*arguments)
63
50
 
64
- FileUtils.mkpath(*args)
51
+ if status != 0
52
+ raise CommandError.new(status)
53
+ end
65
54
  end
55
+ end
56
+
57
+ def mkpath(*args)
58
+ return unless wet?
66
59
 
67
- def install(*args)
68
- return unless wet?
69
-
70
- FileUtils.install(*args)
71
- end
60
+ FileUtils.mkpath(*args)
61
+ end
62
+
63
+ def install(*args)
64
+ return unless wet?
72
65
 
73
- # This function is called to finish the invocation of the task within the graph.
74
- # There are two possible ways this function can generally proceed.
75
- # 1/ The node this task is running for is clean, and thus no actual processing needs to take place, but children should probably be executed.
76
- # 2/ The node this task is running for is dirty, and the execution of commands should work as expected.
77
- def update
78
- @node.evaluate(self)
79
- end
66
+ FileUtils.install(*args)
67
+ end
68
+
69
+ # This function is called to finish the invocation of the task within the graph.
70
+ # There are two possible ways this function can generally proceed.
71
+ # 1/ The node this task is running for is clean, and thus no actual processing needs to take place, but children should probably be executed.
72
+ # 2/ The node this task is running for is dirty, and the execution of commands should work as expected.
73
+ def update
74
+ @node.evaluate(self)
80
75
  end
81
76
  end
@@ -24,43 +24,38 @@ require 'build/graph/walker'
24
24
  require 'build/graph/task'
25
25
  require 'build/files/glob'
26
26
 
27
- module Build::Graph::TaskSpec
28
- include Build::Graph
29
- include Build::Files
30
-
31
- RSpec.describe Build::Graph::Task do
32
- it "should wait for children" do
33
- node_a = Node.new(Paths::NONE, Paths::NONE, "a")
34
- node_b = Node.new(Paths::NONE, Paths::NONE, "b")
27
+ RSpec.describe Build::Graph::Task do
28
+ it "should wait for children" do
29
+ node_a = Build::Graph::Node.new(Build::Files::Paths::NONE, Build::Files::Paths::NONE, "a")
30
+ node_b = Build::Graph::Node.new(Build::Files::Paths::NONE, Build::Files::Paths::NONE, "b")
31
+
32
+ nodes = Set.new([node_a])
33
+
34
+ sequence = []
35
+
36
+ # A walker runs repeatedly, updating tasks which have been marked as dirty.
37
+ walker = Build::Graph::Walker.new do |walker, node|
38
+ task = Build::Graph::Task.new(walker, node)
35
39
 
36
- nodes = Set.new([node_a])
37
-
38
- sequence = []
39
-
40
- # A walker runs repeatedly, updating tasks which have been marked as dirty.
41
- walker = Walker.new do |walker, node|
42
- task = Task.new(walker, node)
40
+ task.visit do
41
+ sequence << node.process.upcase
43
42
 
44
- task.visit do
45
- sequence << node.process.upcase
46
-
47
- if node.process == 'a'
48
- # This will invoke node_b concurrently, but as it is a child, task.visit won't finish until node_b is done.
49
- task.invoke(node_b)
50
- end
43
+ if node.process == 'a'
44
+ # This will invoke node_b concurrently, but as it is a child, task.visit won't finish until node_b is done.
45
+ task.invoke(node_b)
51
46
  end
52
-
53
- sequence << node.process
54
47
  end
55
48
 
56
- walker.update(nodes)
57
-
58
- expect(walker.tasks.count).to be == 2
59
- expect(walker.failed_tasks.count).to be == 0
60
-
61
- task_b = walker.tasks[node_b]
62
- expect(walker.tasks[node_a].children).to be == [task_b]
63
- expect(sequence).to be == ['A', 'B', 'b', 'a']
49
+ sequence << node.process
64
50
  end
51
+
52
+ walker.update(nodes)
53
+
54
+ expect(walker.tasks.count).to be == 2
55
+ expect(walker.failed_tasks.count).to be == 0
56
+
57
+ task_b = walker.tasks[node_b]
58
+ expect(walker.tasks[node_a].children).to be == [task_b]
59
+ expect(sequence).to be == ['A', 'B', 'b', 'a']
65
60
  end
66
61
  end
@@ -24,68 +24,102 @@ require 'build/graph/walker'
24
24
  require 'build/graph/task'
25
25
  require 'build/files/glob'
26
26
 
27
- module Build::Graph::WalkerSpec
28
- include Build::Graph
29
- include Build::Files
27
+ RSpec.describe Build::Graph::Walker do
28
+ let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
30
29
 
31
- RSpec.describe Build::Graph::Walker do
32
- let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
30
+ it "can generate the same output from multiple tasks" do
31
+ test_glob = Build::Files::Glob.new(__dir__, "*.rb")
32
+ listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
33
33
 
34
- it "should be unique" do
35
- test_glob = Glob.new(__dir__, "*.rb")
36
- listing_output = Paths.directory(__dir__, ["listing.txt"])
37
-
38
- node_a = Node.new(test_glob, listing_output, "a")
39
- node_b = Node.new(listing_output, Paths::NONE, "b")
40
-
41
- sequence = []
34
+ node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
35
+ node_b = Build::Graph::Node.new(Build::Files::Paths::NONE, listing_output, "b")
36
+
37
+ sequence = []
38
+
39
+ # A walker runs repeatedly, updating tasks which have been marked as dirty.
40
+ walker = Build::Graph::Walker.new(logger: logger) do |walker, node|
41
+ task = Build::Graph::Task.new(walker, node)
42
42
 
43
- # A walker runs repeatedly, updating tasks which have been marked as dirty.
44
- walker = Walker.new do |walker, node|
45
- task = Task.new(walker, node)
43
+ task.visit do
44
+ if node.process == "a"
45
+ task.invoke(node_b)
46
+ end
46
47
 
47
- task.visit do
48
- sequence << node.process
48
+ node.outputs.each do |output|
49
+ output.touch
49
50
  end
51
+ sequence << node.process
50
52
  end
51
-
52
- walker.update([node_a, node_b])
53
-
54
- expect(walker.tasks.count).to be == 2
55
- expect(walker.failed_tasks.count).to be == 0
56
- expect(sequence).to be == ['a', 'b']
57
53
  end
58
54
 
59
- it "should cascade failure" do
60
- test_glob = Glob.new(__dir__, "*.rb")
61
- listing_output = Paths.directory(__dir__, ["listing.txt"])
62
- summary_output = Paths.directory(__dir__, ["summary.txt"])
63
-
64
- node_a = Node.new(test_glob, listing_output, "a")
65
- node_b = Node.new(listing_output, summary_output, "b")
55
+ edge = double()
56
+ walker.outputs[listing_output.first.to_s] ||= [edge]
57
+ expect(edge).to receive(:traverse)
58
+
59
+ walker.update([node_a, node_a])
60
+
61
+ expect(walker.tasks.count).to be == 2
62
+ expect(walker.failed_tasks.count).to be == 0
63
+ expect(sequence).to be == ['b', 'a']
64
+ end
65
+
66
+ it "should be unique" do
67
+ test_glob = Build::Files::Glob.new(__dir__, "*.rb")
68
+ listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
69
+
70
+ node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
71
+ node_b = Build::Graph::Node.new(listing_output, Build::Files::Paths::NONE, "b")
72
+
73
+ sequence = []
74
+
75
+ # A walker runs repeatedly, updating tasks which have been marked as dirty.
76
+ walker = Build::Graph::Walker.new(logger: logger) do |walker, node|
77
+ task = Build::Graph::Task.new(walker, node)
66
78
 
67
- # A walker runs repeatedly, updating tasks which have been marked as dirty.
68
- walker = Walker.new do |walker, node|
69
- task = Task.new(walker, node)
70
-
71
- task.visit do
72
- if node.process == 'a'
73
- raise TransientError.new('Test Failure')
74
- end
79
+ task.visit do
80
+ node.outputs.each do |output|
81
+ output.touch
75
82
  end
83
+ sequence << node.process
76
84
  end
85
+ end
86
+
87
+ walker.update([node_a, node_b])
88
+
89
+ expect(walker.tasks.count).to be == 2
90
+ expect(walker.failed_tasks.count).to be == 0
91
+ expect(sequence).to be == ['a', 'b']
92
+ end
93
+
94
+ it "should cascade failure" do
95
+ test_glob = Build::Files::Glob.new(__dir__, "*.rb")
96
+ listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
97
+ summary_output = Build::Files::Paths.directory(__dir__, ["summary.txt"])
98
+
99
+ node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
100
+ node_b = Build::Graph::Node.new(listing_output, summary_output, "b")
101
+
102
+ # A walker runs repeatedly, updating tasks which have been marked as dirty.
103
+ walker = Build::Graph::Walker.new do |walker, node|
104
+ task = Build::Graph::Task.new(walker, node)
77
105
 
78
- walker.update([node_a, node_b])
79
-
80
- expect(walker.tasks.count).to be == 2
81
- expect(walker.failed_tasks.count).to be == 2
82
- expect(listing_output).to be_intersect walker.failed_outputs
83
- expect(summary_output).to be_intersect walker.failed_outputs
84
-
85
- walker.clear_failed
86
-
87
- expect(walker.tasks.count).to be == 0
88
- expect(walker.failed_tasks.count).to be == 0
106
+ task.visit do
107
+ if node.process == 'a'
108
+ raise Build::Graph::TransientError.new('Test Failure')
109
+ end
110
+ end
89
111
  end
112
+
113
+ walker.update([node_a, node_b])
114
+
115
+ expect(walker.tasks.count).to be == 2
116
+ expect(walker.failed_tasks.count).to be == 2
117
+ expect(listing_output).to be_intersect walker.failed_outputs
118
+ expect(summary_output).to be_intersect walker.failed_outputs
119
+
120
+ walker.clear_failed
121
+
122
+ expect(walker.tasks.count).to be == 0
123
+ expect(walker.failed_tasks.count).to be == 0
90
124
  end
91
125
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: build-graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams