knapsack_pro 5.3.4 → 5.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/knapsack_pro/formatters/rspec_queue_summary_formatter.rb +28 -0
- data/lib/knapsack_pro/runners/queue/rspec_runner.rb +37 -28
- data/lib/knapsack_pro/tracker.rb +6 -0
- data/lib/knapsack_pro/version.rb +1 -1
- data/spec/knapsack_pro/runners/queue/rspec_runner_spec.rb +146 -89
- data/spec/knapsack_pro/tracker_spec.rb +17 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 202eb37b7550c8556fadd902ed204ea71d5c928b2ed79f3452aa2f7dabee1a35
|
|
4
|
+
data.tar.gz: 846ec0dd9f9953bbbfeed2b9f77535a25e5182de8b3798dcff88a48fce1ba0f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e9e1bffd253bade681efd59cdb43a13c8ae99bb43981dbf8aaea0f7adc3e5d2cce530488e6308a7eb35821abfc9245f26236e3e3dc323b68dbd3fd457905865
|
|
7
|
+
data.tar.gz: aedf904bdf8f765e1fdcbe651ee9d1f839201c48fdad7750668c88cb2f70bb1f61fff1fa633062f0be3bf31770656d48b0467b61b970f6d03526c430b7dc12c8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
### 5.3.5
|
|
4
|
+
|
|
5
|
+
* Handle RSpec exceptions when running RSpec in Queue Mode
|
|
6
|
+
|
|
7
|
+
https://github.com/KnapsackPro/knapsack_pro-ruby/pull/214
|
|
8
|
+
|
|
9
|
+
https://github.com/KnapsackPro/knapsack_pro-ruby/pull/215
|
|
10
|
+
|
|
11
|
+
https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v5.3.4...v5.3.5
|
|
12
|
+
|
|
3
13
|
### 5.3.4
|
|
4
14
|
|
|
5
15
|
* fix(Queue Mode): handle OS signals and RSpec internal `wants_to_quit` and `rspec_is_quitting` states to stop consuming tests from the Queue API when the CI node is terminated
|
|
@@ -62,11 +62,39 @@ module KnapsackPro
|
|
|
62
62
|
unless most_recent_pending.empty?
|
|
63
63
|
registered_output.puts('All pending tests on this CI node:')
|
|
64
64
|
registered_output.puts(most_recent_pending)
|
|
65
|
+
registered_output.puts('')
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
unless most_recent_failures_summary.empty?
|
|
68
69
|
registered_output.puts('All failed tests on this CI node:')
|
|
69
70
|
registered_output.puts(most_recent_failures_summary)
|
|
71
|
+
registered_output.puts('')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
registered_output.puts(most_recent_summary)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.print_exit_summary
|
|
78
|
+
registered_output.puts('Knapsack Pro Queue exited/aborted!')
|
|
79
|
+
registered_output.puts('')
|
|
80
|
+
|
|
81
|
+
unexecuted_test_files = KnapsackPro.tracker.unexecuted_test_files
|
|
82
|
+
unless unexecuted_test_files.empty?
|
|
83
|
+
registered_output.puts('Unexecuted tests on this CI node:')
|
|
84
|
+
registered_output.puts(unexecuted_test_files)
|
|
85
|
+
registered_output.puts('')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
unless most_recent_pending.empty?
|
|
89
|
+
registered_output.puts('All pending tests on this CI node:')
|
|
90
|
+
registered_output.puts(most_recent_pending)
|
|
91
|
+
registered_output.puts('')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
unless most_recent_failures_summary.empty?
|
|
95
|
+
registered_output.puts('All failed tests on this CI node:')
|
|
96
|
+
registered_output.puts(most_recent_failures_summary)
|
|
97
|
+
registered_output.puts('')
|
|
70
98
|
end
|
|
71
99
|
|
|
72
100
|
registered_output.puts(most_recent_summary)
|
|
@@ -92,35 +92,44 @@ module KnapsackPro
|
|
|
92
92
|
options = ::RSpec::Core::ConfigurationOptions.new(cli_args)
|
|
93
93
|
rspec_runner = ::RSpec::Core::Runner.new(options)
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
KnapsackPro.logger.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
95
|
+
begin
|
|
96
|
+
exit_code = rspec_runner.run($stderr, $stdout)
|
|
97
|
+
exitstatus = exit_code if exit_code != 0
|
|
98
|
+
rescue Exception => exception
|
|
99
|
+
KnapsackPro.logger.error("Having exception when running RSpec: #{exception.inspect}")
|
|
100
|
+
KnapsackPro::Formatters::RSpecQueueSummaryFormatter.print_exit_summary
|
|
101
|
+
KnapsackPro::Hooks::Queue.call_after_subset_queue
|
|
102
|
+
KnapsackPro::Hooks::Queue.call_after_queue
|
|
103
|
+
Kernel.exit(1)
|
|
104
|
+
raise
|
|
105
|
+
else
|
|
106
|
+
if rspec_runner.world.wants_to_quit
|
|
107
|
+
KnapsackPro.logger.warn('RSpec wants to quit.')
|
|
108
|
+
set_terminate_process
|
|
109
|
+
end
|
|
110
|
+
if rspec_runner.world.rspec_is_quitting
|
|
111
|
+
KnapsackPro.logger.warn('RSpec is quitting.')
|
|
112
|
+
set_terminate_process
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
printable_args = args_with_seed_option_added_when_viable(args, rspec_runner)
|
|
116
|
+
log_rspec_command(printable_args, test_file_paths, :subset_queue)
|
|
117
|
+
|
|
118
|
+
rspec_clear_examples
|
|
119
|
+
|
|
120
|
+
KnapsackPro::Hooks::Queue.call_after_subset_queue
|
|
121
|
+
|
|
122
|
+
KnapsackPro::Report.save_subset_queue_to_file
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
status: :next,
|
|
126
|
+
runner: runner,
|
|
127
|
+
can_initialize_queue: false,
|
|
128
|
+
args: args,
|
|
129
|
+
exitstatus: exitstatus,
|
|
130
|
+
all_test_file_paths: all_test_file_paths,
|
|
131
|
+
}
|
|
105
132
|
end
|
|
106
|
-
|
|
107
|
-
printable_args = args_with_seed_option_added_when_viable(args, rspec_runner)
|
|
108
|
-
log_rspec_command(printable_args, test_file_paths, :subset_queue)
|
|
109
|
-
|
|
110
|
-
rspec_clear_examples
|
|
111
|
-
|
|
112
|
-
KnapsackPro::Hooks::Queue.call_after_subset_queue
|
|
113
|
-
|
|
114
|
-
KnapsackPro::Report.save_subset_queue_to_file
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
status: :next,
|
|
118
|
-
runner: runner,
|
|
119
|
-
can_initialize_queue: false,
|
|
120
|
-
args: args,
|
|
121
|
-
exitstatus: exitstatus,
|
|
122
|
-
all_test_file_paths: all_test_file_paths,
|
|
123
|
-
}
|
|
124
133
|
end
|
|
125
134
|
end
|
|
126
135
|
|
data/lib/knapsack_pro/tracker.rb
CHANGED
|
@@ -68,6 +68,12 @@ module KnapsackPro
|
|
|
68
68
|
@prerun_tests_loaded = true
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
def unexecuted_test_files
|
|
72
|
+
@test_files_with_time.map do |path, hash|
|
|
73
|
+
path unless hash[:measured_time]
|
|
74
|
+
end.compact
|
|
75
|
+
end
|
|
76
|
+
|
|
71
77
|
def to_a
|
|
72
78
|
# When the test files are not loaded in the memory then load them from the disk.
|
|
73
79
|
# Useful for the Regular Mode when the memory is not shared between tracker instances.
|
data/lib/knapsack_pro/version.rb
CHANGED
|
@@ -212,126 +212,183 @@ describe KnapsackPro::Runners::Queue::RSpecRunner do
|
|
|
212
212
|
double(world: double(wants_to_quit: rspec_wants_to_quit, rspec_is_quitting: rspec_is_quitting))
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
context 'having no exception when running RSpec' do
|
|
216
|
+
before do
|
|
217
|
+
subset_queue_id = 'fake-subset-queue-id'
|
|
218
|
+
expect(KnapsackPro::Config::EnvGenerator).to receive(:set_subset_queue_id).and_return(subset_queue_id)
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_SUBSET_QUEUE_ID', subset_queue_id)
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
tracker = instance_double(KnapsackPro::Tracker)
|
|
223
|
+
expect(KnapsackPro).to receive(:tracker).twice.and_return(tracker)
|
|
224
|
+
expect(tracker).to receive(:reset!)
|
|
225
|
+
expect(tracker).to receive(:set_prerun_tests).with(test_file_paths)
|
|
225
226
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
227
|
+
expect(described_class).to receive(:ensure_spec_opts_have_rspec_queue_summary_formatter)
|
|
228
|
+
options = double
|
|
229
|
+
expect(RSpec::Core::ConfigurationOptions).to receive(:new).with([
|
|
230
|
+
'--no-color',
|
|
231
|
+
'--default-path', 'fake-test-dir',
|
|
232
|
+
'a_spec.rb', 'b_spec.rb',
|
|
233
|
+
]).and_return(options)
|
|
233
234
|
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
expect(RSpec::Core::Runner).to receive(:new).with(options).and_return(rspec_core_runner)
|
|
236
|
+
expect(rspec_core_runner).to receive(:run).with($stderr, $stdout).and_return(exit_code)
|
|
236
237
|
|
|
237
|
-
|
|
238
|
+
expect(described_class).to receive(:rspec_clear_examples)
|
|
238
239
|
|
|
239
|
-
|
|
240
|
+
expect(KnapsackPro::Hooks::Queue).to receive(:call_before_subset_queue)
|
|
240
241
|
|
|
241
|
-
|
|
242
|
+
expect(KnapsackPro::Hooks::Queue).to receive(:call_after_subset_queue)
|
|
242
243
|
|
|
243
|
-
|
|
244
|
+
expect(KnapsackPro::Report).to receive(:save_subset_queue_to_file)
|
|
244
245
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
configuration = double
|
|
247
|
+
expect(rspec_core_runner).to receive(:configuration).twice.and_return(configuration)
|
|
248
|
+
expect(configuration).to receive(:seed_used?).and_return(true)
|
|
249
|
+
expect(configuration).to receive(:seed).and_return(rspec_seed)
|
|
249
250
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
expect(KnapsackPro).to receive(:logger).at_least(2).and_return(logger)
|
|
252
|
+
expect(logger).to receive(:info)
|
|
253
|
+
.with("To retry the last batch of tests fetched from the API Queue, please run the following command on your machine:")
|
|
254
|
+
expect(logger).to receive(:info).with(/#{args.join(' ')} --seed #{rspec_seed}/)
|
|
255
|
+
end
|
|
255
256
|
|
|
256
|
-
|
|
257
|
-
|
|
257
|
+
context 'when the exit code is zero' do
|
|
258
|
+
let(:exit_code) { 0 }
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
260
|
+
it do
|
|
261
|
+
expect(subject).to eq({
|
|
262
|
+
status: :next,
|
|
263
|
+
runner: runner,
|
|
264
|
+
can_initialize_queue: false,
|
|
265
|
+
args: args,
|
|
266
|
+
exitstatus: exitstatus,
|
|
267
|
+
all_test_file_paths: test_file_paths,
|
|
268
|
+
})
|
|
269
|
+
end
|
|
268
270
|
end
|
|
269
|
-
end
|
|
270
271
|
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
context 'when the exit code is not zero' do
|
|
273
|
+
let(:exit_code) { double }
|
|
273
274
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
275
|
+
it do
|
|
276
|
+
expect(subject).to eq({
|
|
277
|
+
status: :next,
|
|
278
|
+
runner: runner,
|
|
279
|
+
can_initialize_queue: false,
|
|
280
|
+
args: args,
|
|
281
|
+
exitstatus: exit_code,
|
|
282
|
+
all_test_file_paths: test_file_paths,
|
|
283
|
+
})
|
|
284
|
+
end
|
|
283
285
|
end
|
|
284
|
-
end
|
|
285
286
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
context 'when RSpec wants to quit' do
|
|
288
|
+
let(:exit_code) { 0 }
|
|
289
|
+
let(:rspec_wants_to_quit) { true }
|
|
290
|
+
|
|
291
|
+
after do
|
|
292
|
+
described_class.class_variable_set(:@@terminate_process, false)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it 'terminates the process' do
|
|
296
|
+
expect(logger).to receive(:warn).with('RSpec wants to quit.')
|
|
289
297
|
|
|
290
|
-
|
|
291
|
-
|
|
298
|
+
expect(described_class.class_variable_get(:@@terminate_process)).to be false
|
|
299
|
+
|
|
300
|
+
expect(subject).to eq({
|
|
301
|
+
status: :next,
|
|
302
|
+
runner: runner,
|
|
303
|
+
can_initialize_queue: false,
|
|
304
|
+
args: args,
|
|
305
|
+
exitstatus: exitstatus,
|
|
306
|
+
all_test_file_paths: test_file_paths,
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
expect(described_class.class_variable_get(:@@terminate_process)).to be true
|
|
310
|
+
end
|
|
292
311
|
end
|
|
293
312
|
|
|
294
|
-
|
|
295
|
-
|
|
313
|
+
context 'when RSpec is quitting' do
|
|
314
|
+
let(:exit_code) { 0 }
|
|
315
|
+
let(:rspec_is_quitting) { true }
|
|
296
316
|
|
|
297
|
-
|
|
317
|
+
after do
|
|
318
|
+
described_class.class_variable_set(:@@terminate_process, false)
|
|
319
|
+
end
|
|
298
320
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
runner: runner,
|
|
302
|
-
can_initialize_queue: false,
|
|
303
|
-
args: args,
|
|
304
|
-
exitstatus: exitstatus,
|
|
305
|
-
all_test_file_paths: test_file_paths,
|
|
306
|
-
})
|
|
321
|
+
it 'terminates the process' do
|
|
322
|
+
expect(logger).to receive(:warn).with('RSpec is quitting.')
|
|
307
323
|
|
|
308
|
-
|
|
324
|
+
expect(described_class.class_variable_get(:@@terminate_process)).to be false
|
|
325
|
+
|
|
326
|
+
expect(subject).to eq({
|
|
327
|
+
status: :next,
|
|
328
|
+
runner: runner,
|
|
329
|
+
can_initialize_queue: false,
|
|
330
|
+
args: args,
|
|
331
|
+
exitstatus: exitstatus,
|
|
332
|
+
all_test_file_paths: test_file_paths,
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
expect(described_class.class_variable_get(:@@terminate_process)).to be true
|
|
336
|
+
end
|
|
309
337
|
end
|
|
310
338
|
end
|
|
311
339
|
|
|
312
|
-
context 'when RSpec
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
340
|
+
context 'having exception when running RSpec' do
|
|
341
|
+
before do
|
|
342
|
+
subset_queue_id = 'fake-subset-queue-id'
|
|
343
|
+
expect(KnapsackPro::Config::EnvGenerator).to receive(:set_subset_queue_id).and_return(subset_queue_id)
|
|
344
|
+
|
|
345
|
+
expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_SUBSET_QUEUE_ID', subset_queue_id)
|
|
346
|
+
|
|
347
|
+
tracker = instance_double(KnapsackPro::Tracker)
|
|
348
|
+
expect(KnapsackPro).to receive(:tracker).twice.and_return(tracker)
|
|
349
|
+
expect(tracker).to receive(:reset!)
|
|
350
|
+
expect(tracker).to receive(:set_prerun_tests).with(test_file_paths)
|
|
351
|
+
|
|
352
|
+
expect(described_class).to receive(:ensure_spec_opts_have_rspec_queue_summary_formatter)
|
|
353
|
+
options = double
|
|
354
|
+
expect(RSpec::Core::ConfigurationOptions).to receive(:new).with([
|
|
355
|
+
'--no-color',
|
|
356
|
+
'--default-path', 'fake-test-dir',
|
|
357
|
+
'a_spec.rb', 'b_spec.rb',
|
|
358
|
+
]).and_return(options)
|
|
359
|
+
|
|
360
|
+
rspec_core_runner = double
|
|
361
|
+
expect(RSpec::Core::Runner).to receive(:new).with(options).and_return(rspec_core_runner)
|
|
362
|
+
expect(rspec_core_runner).to receive(:run).with($stderr, $stdout).and_raise SystemExit
|
|
363
|
+
expect(KnapsackPro::Hooks::Queue).to receive(:call_before_subset_queue)
|
|
364
|
+
allow(KnapsackPro::Report).to receive(:save_subset_queue_to_file)
|
|
365
|
+
allow(KnapsackPro::Hooks::Queue).to receive(:call_after_subset_queue)
|
|
366
|
+
allow(KnapsackPro::Hooks::Queue).to receive(:call_after_queue)
|
|
367
|
+
allow(KnapsackPro::Formatters::RSpecQueueSummaryFormatter).to receive(:print_exit_summary)
|
|
368
|
+
expect(Kernel).to receive(:exit).with(1)
|
|
318
369
|
end
|
|
319
370
|
|
|
320
|
-
it '
|
|
321
|
-
expect(
|
|
371
|
+
it 'does not call #save_subset_queue_to_file or #rspec_clear_examples' do
|
|
372
|
+
expect(described_class).not_to receive(:rspec_clear_examples)
|
|
373
|
+
expect(KnapsackPro::Report).not_to receive(:save_subset_queue_to_file)
|
|
374
|
+
expect { subject }.to raise_error SystemExit
|
|
375
|
+
end
|
|
322
376
|
|
|
323
|
-
|
|
377
|
+
it 'logs the exception' do
|
|
378
|
+
expect(KnapsackPro).to receive(:logger).once.and_return(logger)
|
|
379
|
+
expect(logger).to receive(:error).with("Having exception when running RSpec: #<SystemExit: SystemExit>")
|
|
380
|
+
expect { subject }.to raise_error SystemExit
|
|
381
|
+
end
|
|
324
382
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
args: args,
|
|
330
|
-
exitstatus: exitstatus,
|
|
331
|
-
all_test_file_paths: test_file_paths,
|
|
332
|
-
})
|
|
383
|
+
it 'calls #print_exit_summary' do
|
|
384
|
+
expect(KnapsackPro::Formatters::RSpecQueueSummaryFormatter).to receive(:print_exit_summary)
|
|
385
|
+
expect { subject }.to raise_error SystemExit
|
|
386
|
+
end
|
|
333
387
|
|
|
334
|
-
|
|
388
|
+
it 'calls #call_after_subset_queue and #call_after_queue' do
|
|
389
|
+
expect(KnapsackPro::Hooks::Queue).to receive(:call_after_subset_queue)
|
|
390
|
+
expect(KnapsackPro::Hooks::Queue).to receive(:call_after_queue)
|
|
391
|
+
expect { subject }.to raise_error SystemExit
|
|
335
392
|
end
|
|
336
393
|
end
|
|
337
394
|
end
|
|
@@ -185,4 +185,21 @@ describe KnapsackPro::Tracker do
|
|
|
185
185
|
expect(tracker.prerun_tests_loaded).to be false
|
|
186
186
|
end
|
|
187
187
|
end
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
describe '#unexecuted_test_files' do
|
|
191
|
+
before do
|
|
192
|
+
tracker.set_prerun_tests(['a_spec.rb', 'b_spec.rb', 'c_spec.rb'])
|
|
193
|
+
|
|
194
|
+
# measure execution time for b_spec.rb
|
|
195
|
+
tracker.current_test_path = 'b_spec.rb'
|
|
196
|
+
tracker.start_timer
|
|
197
|
+
sleep 0.1
|
|
198
|
+
tracker.stop_timer
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it 'returns test files without measured time' do
|
|
202
|
+
expect(tracker.unexecuted_test_files).to eq(['a_spec.rb', 'c_spec.rb'])
|
|
203
|
+
end
|
|
204
|
+
end
|
|
188
205
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: knapsack_pro
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.3.
|
|
4
|
+
version: 5.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ArturT
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-
|
|
11
|
+
date: 2023-08-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|