-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathparallel_spec.rb
More file actions
287 lines (251 loc) · 11.1 KB
/
parallel_spec.rb
File metadata and controls
287 lines (251 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# frozen_string_literal: true
require 'spec_helper'
require 'bolt_spec/conn'
require 'bolt_spec/files'
require 'bolt_spec/integration'
require 'bolt_spec/project'
describe 'plans' do
include BoltSpec::Integration
include BoltSpec::Files
include BoltSpec::Conn
include BoltSpec::Project
after(:each) { Puppet.settings.send(:clear_everything_for_tests) }
let(:modulepath) { fixtures_path('parallel') }
shared_examples "parallelize plan function" do
let(:return_value) {
{ "action" => "task",
"object" => "parallel",
"status" => "success",
"value" => { "_output" => /print/ } }
}
it "returns an array of Results" do
results = run_cli_json(%w[plan run parallel] + config_flags)
results.each do |result|
expect(result).to be_a(Array)
expect(result.length).to eq(1)
expect(result.first).to include(return_value)
end
end
it "returns from a return statement", ssh: true do
before_time = Time.now
results = run_cli_json(%w[plan run parallel::return] + config_flags)
wall_time = Time.now - before_time
# Assert we don't execute the sleep
expect(wall_time).to be < 2
results.each do |result|
expect(result).to eq('a')
end
end
it "does not raise an error when catch_errors wraps the block" do
result = run_cli_json(%w[plan run parallel::catch_error_outer] + config_flags)
expect(result).to eq("We made it")
end
it "does not raise an error when catch_errors is inside the block" do
result = run_cli_json(%w[plan run parallel::catch_error_inner] + config_flags)
expect(result).to eq("We made it")
end
it "fails immediately when a Puppet error is raised" do
result = run_cli_json(%w[plan run parallel::hard_fail] + config_flags)
expect(result['msg']).to match(/This Name has no effect./)
end
it "runs normally when no steps are parallelizable" do
result = run_cli_json(%w[plan run parallel::non_parallel] + config_flags)
expect(result).to eq("Success")
end
it "runs when provided an array with duplicate objects" do
end
it "does not lose scope when calling an executor function from a custom Puppet language function" do
result = run_cli_json(%W[plan run parallel::custom_function -m #{modulepath}])
expect(result).to eq('localhost')
end
end
shared_examples "#background()" do
it "returns a Future object immediately" do
# CODEREVIEW: Is it worse to have this on one line, or like this? Using
# Regexp::EXTENDED doesn't work.
regex = %r{Returned immediately
Type of Future '\d'
Finished: plan background::timing .*
Starting backgrounded block
Plan completed successfully}
output = run_cli(%w[plan run background::timing] + config_flags,
outputter: Bolt::Outputter::Human)
expect(output).to match(regex)
end
it "includes variables, including undef vars, from the plan in scope" do
regex = %r{Starting: plan background::variables
In main plan: After background
Finished: plan background::variables.*
Inside background: Before background
Undef:}
output = run_cli(%w[plan run background::variables] + config_flags,
outputter: Bolt::Outputter::Human)
expect(output).to match(regex)
expect(output).to match(/Unknown variable: 'foo'/)
expect(output).not_to match(/Unknown variable: 'undef'/)
end
it "returns from a 'return' statement'" do
output = run_cli_json(%w[plan run background::return] + config_flags)
expect(output.first).to eq("Return me!")
end
it "does not fail the plan if errors are raised before main plan finishes" do
output = run_cli_json(%w[plan run background::error sleep=true] + config_flags)
expect(output).to eq("Still ran successfully")
expect(@log_output.readlines).to include(/INFO Bolt::Executor.*The command failed/)
end
it "does not fail the plan if errors are raised after main plan finishes" do
output = run_cli_json(%w[plan run background::error] + config_flags)
expect(output).to eq("Still ran successfully")
expect(@log_output.readlines).to include(/WARN.*run_command 'exit 1' failed/)
end
end
shared_examples "#wait()" do
context "without futures" do
it "waits for Futures from the calling plan to finish" do
result = run_cli_json(%w[plan run wait::no_future::basic] + config_flags)
expect(result)
.to include("Who's on first", "What's on second", "I don't know's on third", "Run immediately")
end
# `wait()` will return the results from everything it waited on, so
# instead of checking timing checking the result should guarantee that
# these Futures were the only ones waited on.
it "does not wait for Futures from other plans executing in parallel" do
result = run_cli_json(%w[plan run wait::no_future::subplan] + config_flags)
expect(result).to eq(["Just a subplan, hold the mustard"])
end
it "errors when called inside a `background()` in the same plan" do
run_cli_json(%w[plan run wait::no_future::infinite_loop] + config_flags)
expect(@log_output.readlines).to include(/The wait\(\) function cannot be called/)
end
it "waits on any Futures created inside a `background()` block in the same plan" do
result = run_cli_json(%w[plan run wait::no_future::inner_bg] + config_flags)
expect(result).to contain_exactly("Thing 1", "Thing 2")
end
end
context "without a timeout" do
it 'blocks until all futures have finished' do
expected = <<~OUT
[
"I don't know's on third",
"What's on second",
"Who's on first"
]
That's what I want to find out.
OUT
output = run_cli(%w[plan run wait] + config_flags, outputter: Bolt::Outputter::Human)
expect(output).to include(expected)
end
it "doesn't include results from futures not passed to the function" do
expected = <<~OUT
[
"What's on second"
]
That's what I want to find out.
OUT
output = run_cli(%w[plan run wait start=1 end=1] + config_flags, outputter: Bolt::Outputter::Human)
expect(output).to include(expected)
end
it "doesn't wait for inner Futures to finish" do
output = run_cli(%w[plan run wait::inner_future] + config_flags, outputter: Bolt::Outputter::Human)
expect(output).to include("Before inner future\nFinished: plan wait")
end
it 'continues if one future errors, and raises a ParallelFailure' do
output = run_cli(%w[plan run wait::error] + config_flags,
outputter: Bolt::Outputter::Human)
expect(output).to include("Who's on first\nI don't know's on third")
expect(output).to include("parallel block failed on 1 target")
expect(output).not_to include("Finished main plan.")
end
it 'returns errors if _catch_errors is passed' do
output = run_cli(%w[plan run wait::error catch_errors=true] + config_flags,
outputter: Bolt::Outputter::Human)
expect(output).to include("Who's on first\nI don't know's on third")
expect(output).to include("run_command 'exit 1' failed")
expect(output).to include("Finished main plan.")
end
end
context "with a timeout" do
it 'returns once fibers have finished if timeout is longer' do
start = Time.now
run_cli(%w[plan run wait::timeout timeout=20] + config_flags)
expect(Time.now - start).to be < 20
end
it 'raises a Timeout error if timeout is exceeded' do
params = { 'timeout' => 0.1, 'sleep' => 0.5 }.to_json
output = run_cli_json(%W[plan run wait::timeout --params #{params}] + config_flags)
expect(output['kind']).to eq("bolt/parallel-failure")
expect(output['msg']).to match(/parallel block failed/)
expect(output['details']).to include({ "action" => "parallelize", "failed_indices" => [1] })
end
it 'returns Timeout errors if _catch_errors is provided' do
params = { 'timeout' => 0.1, 'sleep' => 0.5, 'catch_errors' => true }.to_json
output = run_cli_json(%W[plan run wait::timeout --params #{params}] + config_flags)
expect(output).to eq("Finished the plan")
end
end
end
context "over ssh", ssh: true do
let(:inv_path) { fixtures_path('inventory', 'docker.yaml') }
let(:config_flags) {
['-t all',
'--modulepath', modulepath,
'--inventoryfile', inv_path,
'--verbose',
'--no-host-key-check']
}
include_examples 'parallelize plan function'
include_examples '#background()'
include_examples '#wait()'
it "finishes executing the block then raises an error when there's an error" do
expected_err = { "kind" => "bolt/parallel-failure",
"msg" => "parallel block failed on 1 target" }
expected_details = { "action" => "parallelize",
"failed_indices" => [1] }
expected_results = [[{ "target" => 'ubuntu_node',
"action" => "task",
"object" => "parallel",
"status" => "success",
"value" => { "_output" => "a\n" } }],
{ "kind" => "bolt/run-failure",
"msg" => "run_task 'error::fail' failed on 1 target",
"details" =>
{ "action" => "run_task",
"object" => "error::fail",
"result_set" =>
[{ "target" => 'puppet_8_node',
"action" => "task",
"object" => "error::fail",
"status" => "failure",
"value" =>
{ "_output" => "failing\n",
"_error" =>
{ "kind" => "puppetlabs.tasks/task-error",
"issue_code" => "TASK_ERROR",
"msg" => "The task failed with exit code 1",
"details" => {
"file" => fixtures_path('parallel', 'parallel', 'plans', 'error.pp'),
"line" => 7,
"exit_code" => 1
} } } }] } }]
result = run_cli_json(%w[plan run parallel::error] + config_flags)
expect(result).to include(expected_err)
expect(result['details']).to include(expected_details)
expect(expected_results - result['details']['results']).to eq([])
end
end
context "over winrm", winrm: true do
let(:targets) { conn_uri('winrm') }
let(:config_flags) {
['-t', targets,
'--modulepath', modulepath,
'--verbose',
'--password', conn_info('winrm')[:password],
'--no-ssl',
'--no-ssl-verify',
'--connect-timeout', '120']
}
include_examples 'parallelize plan function'
include_examples '#background()'
include_examples '#wait()'
end
end