knapsack_pro 6.0.4 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +80 -19
  3. data/.github/pull_request_template.md +22 -0
  4. data/.gitignore +4 -0
  5. data/CHANGELOG.md +87 -0
  6. data/Gemfile +9 -0
  7. data/README.md +0 -4
  8. data/knapsack_pro.gemspec +2 -1
  9. data/lib/knapsack_pro/adapters/base_adapter.rb +7 -2
  10. data/lib/knapsack_pro/adapters/cucumber_adapter.rb +1 -3
  11. data/lib/knapsack_pro/adapters/rspec_adapter.rb +16 -9
  12. data/lib/knapsack_pro/config/env.rb +1 -9
  13. data/lib/knapsack_pro/extensions/rspec_extension.rb +137 -0
  14. data/lib/knapsack_pro/formatters/time_tracker.rb +10 -26
  15. data/lib/knapsack_pro/formatters/time_tracker_fetcher.rb +6 -0
  16. data/lib/knapsack_pro/presenter.rb +1 -1
  17. data/lib/knapsack_pro/pure/queue/rspec_pure.rb +92 -0
  18. data/lib/knapsack_pro/runners/queue/base_runner.rb +6 -1
  19. data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +6 -6
  20. data/lib/knapsack_pro/runners/queue/minitest_runner.rb +6 -6
  21. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +124 -173
  22. data/lib/knapsack_pro/urls.rb +2 -0
  23. data/lib/knapsack_pro/version.rb +1 -1
  24. data/lib/knapsack_pro.rb +1 -0
  25. data/spec/integration/runners/queue/rspec_runner.rb +80 -0
  26. data/spec/integration/runners/queue/rspec_runner_spec.rb +2232 -0
  27. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +17 -11
  28. data/spec/knapsack_pro/adapters/cucumber_adapter_spec.rb +2 -5
  29. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +2 -24
  30. data/spec/knapsack_pro/config/env_spec.rb +1 -35
  31. data/spec/knapsack_pro/formatters/time_tracker_specs.rb +8 -37
  32. data/spec/knapsack_pro/hooks/queue_spec.rb +2 -2
  33. data/spec/knapsack_pro/presenter_spec.rb +1 -1
  34. data/spec/knapsack_pro/pure/queue/rspec_pure_spec.rb +224 -0
  35. data/spec/knapsack_pro/runners/queue/cucumber_runner_spec.rb +16 -16
  36. data/spec/knapsack_pro/runners/queue/minitest_runner_spec.rb +14 -14
  37. data/spec/knapsack_pro_spec.rb +3 -3
  38. metadata +17 -12
  39. data/lib/knapsack_pro/formatters/rspec_queue_profile_formatter_extension.rb +0 -58
  40. data/lib/knapsack_pro/formatters/rspec_queue_summary_formatter.rb +0 -145
  41. data/spec/knapsack_pro/runners/queue/rspec_runner_spec.rb +0 -536
@@ -0,0 +1,2232 @@
1
+ require 'open3'
2
+ require 'json'
3
+ require 'nokogiri'
4
+
5
+ describe "#{KnapsackPro::Runners::Queue::RSpecRunner} - Integration tests", :clear_tmp do
6
+ SPEC_DIRECTORY = 'spec_integration'
7
+
8
+ class Spec
9
+ attr_reader :path, :content
10
+
11
+ def initialize(path, content)
12
+ @path = "#{SPEC_DIRECTORY}/#{path}"
13
+ @content = content
14
+ end
15
+ end
16
+
17
+ # @param rspec_options String
18
+ # @param spec_batches Array[Array[String]]
19
+ def generate_specs(spec_helper, rspec_options, spec_batches)
20
+ ENV['TEST__RSPEC_OPTIONS'] = rspec_options
21
+
22
+ spec_helper_path = "#{SPEC_DIRECTORY}/spec_helper.rb"
23
+ File.open(spec_helper_path, 'w') { |file| file.write(spec_helper) }
24
+
25
+ paths = spec_batches.flatten.map do |spec_item|
26
+ File.open(spec_item.path, 'w') { |file| file.write(spec_item.content) }
27
+ spec_item.path
28
+ end
29
+
30
+ stub_spec_batches(
31
+ spec_batches.map { _1.map(&:path) }
32
+ )
33
+ end
34
+
35
+ def stub_spec_batches(batched_tests)
36
+ ENV['TEST__SPEC_BATCHES'] = batched_tests.to_json
37
+ end
38
+
39
+ # @param test_file_paths Array[String]
40
+ # Example: ['spec_integration/a_spec.rb[1:1]']
41
+ def mock_test_cases_for_slow_test_files(test_file_paths)
42
+ ENV['TEST__TEST_FILE_CASES_FOR_SLOW_TEST_FILES'] = test_file_paths.to_json
43
+ end
44
+
45
+ def log_command_result(stdout, stderr, status)
46
+ return if ENV['TEST__SHOW_DEBUG_LOG'] != 'true'
47
+
48
+ puts '='*50
49
+ puts 'STDOUT:'
50
+ puts stdout
51
+ puts
52
+
53
+ puts '='*50
54
+ puts 'STDERR:'
55
+ puts stderr
56
+ puts
57
+
58
+ puts '='*50
59
+ puts 'Exit status code:'
60
+ puts status
61
+ puts
62
+ end
63
+
64
+ let(:spec_helper_with_knapsack) do
65
+ <<~SPEC
66
+ require 'knapsack_pro'
67
+ KnapsackPro::Adapters::RSpecAdapter.bind
68
+ SPEC
69
+ end
70
+
71
+ subject do
72
+ command = 'ruby spec/integration/runners/queue/rspec_runner.rb'
73
+ stdout, stderr, status = Open3.capture3(command)
74
+ log_command_result(stdout, stderr, status)
75
+ OpenStruct.new(stdout: stdout, stderr: stderr, exit_code: status.exitstatus)
76
+ end
77
+
78
+ before do
79
+ FileUtils.mkdir_p(SPEC_DIRECTORY)
80
+
81
+ ENV['KNAPSACK_PRO_LOG_LEVEL'] = 'debug'
82
+ # Useful when creating or editing a test:
83
+ # ENV['TEST__SHOW_DEBUG_LOG'] = 'true'
84
+ end
85
+ after do
86
+ FileUtils.rm_rf(SPEC_DIRECTORY)
87
+ FileUtils.mkdir_p(SPEC_DIRECTORY)
88
+
89
+ ENV.delete('KNAPSACK_PRO_LOG_LEVEL')
90
+ ENV.keys.select { _1.start_with?('TEST__') }.each do |key|
91
+ ENV.delete(key)
92
+ end
93
+ end
94
+
95
+ context 'when a few batches of tests returned by the Queue API' do
96
+ it 'runs tests' do
97
+ rspec_options = '--format d'
98
+
99
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
100
+ describe 'A_describe' do
101
+ it 'A1 test example' do
102
+ expect(1).to eq 1
103
+ end
104
+ end
105
+ SPEC
106
+
107
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
108
+ describe 'B_describe' do
109
+ it 'B1 test example' do
110
+ expect(1).to eq 1
111
+ end
112
+ end
113
+ SPEC
114
+
115
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
116
+ describe 'C_describe' do
117
+ it 'C1 test example' do
118
+ expect(1).to eq 1
119
+ end
120
+ end
121
+ SPEC
122
+
123
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
124
+ [spec_a, spec_b],
125
+ [spec_c],
126
+ ])
127
+
128
+ actual = subject
129
+
130
+ expect(actual.stdout).to include('DEBUG -- : [knapsack_pro] Queue Mode enabled.')
131
+
132
+ expect(actual.stdout).to include('A1 test example')
133
+ expect(actual.stdout).to include('B1 test example')
134
+ expect(actual.stdout).to include('C1 test example')
135
+
136
+ expect(actual.stdout).to include('INFO -- : [knapsack_pro] To retry the last batch of tests fetched from the Queue API, please run the following command on your machine:')
137
+ expect(actual.stdout).to include('INFO -- : [knapsack_pro] bundle exec rspec --format d --default-path spec_integration "spec_integration/a_spec.rb" "spec_integration/b_spec.rb"')
138
+ expect(actual.stdout).to include('INFO -- : [knapsack_pro] bundle exec rspec --format d --default-path spec_integration "spec_integration/c_spec.rb"')
139
+
140
+ expect(actual.stdout).to include('INFO -- : [knapsack_pro] To retry all the tests assigned to this CI node, please run the following command on your machine:')
141
+ expect(actual.stdout).to include('INFO -- : [knapsack_pro] bundle exec rspec --format d --default-path spec_integration "spec_integration/a_spec.rb" "spec_integration/b_spec.rb" "spec_integration/c_spec.rb"')
142
+
143
+ expect(actual.stdout).to include('3 examples, 0 failures')
144
+
145
+ expect(actual.stdout).to include('DEBUG -- : [knapsack_pro] Global test execution duration:')
146
+
147
+ expect(actual.exit_code).to eq 0
148
+ end
149
+
150
+ it 'detects test execution times correctly before sending it to API' do
151
+ ENV['TEST__LOG_EXECUTION_TIMES'] = 'true'
152
+
153
+ rspec_options = '--format d'
154
+
155
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
156
+ describe 'A_describe' do
157
+ it 'A1 test example' do
158
+ expect(1).to eq 1
159
+ end
160
+ end
161
+ SPEC
162
+
163
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
164
+ describe 'B_describe' do
165
+ it 'B1 test example' do
166
+ expect(1).to eq 1
167
+ end
168
+ end
169
+ SPEC
170
+
171
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
172
+ describe 'C_describe' do
173
+ it 'C1 test example' do
174
+ expect(1).to eq 1
175
+ end
176
+ end
177
+ SPEC
178
+
179
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
180
+ [spec_a, spec_b],
181
+ [spec_c],
182
+ ])
183
+
184
+ actual = subject
185
+
186
+ expect(actual.stdout).to include('[INTEGRATION TEST] test_files: 3, test files have execution time: true')
187
+
188
+ expect(actual.exit_code).to eq 0
189
+ end
190
+ end
191
+
192
+ context 'when spec_helper.rb has a missing KnapsackPro::Adapters::RSpecAdapter.bind method' do
193
+ it do
194
+ rspec_options = ''
195
+
196
+ spec_helper = <<~SPEC
197
+ require 'knapsack_pro'
198
+ SPEC
199
+
200
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
201
+ describe 'A_describe' do
202
+ it 'A1 test example' do
203
+ expect(1).to eq 1
204
+ end
205
+ end
206
+ SPEC
207
+
208
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
209
+ describe 'B_describe' do
210
+ it 'B1 test example' do
211
+ expect(1).to eq 1
212
+ end
213
+ end
214
+ SPEC
215
+
216
+ generate_specs(spec_helper, rspec_options, [
217
+ [spec_a],
218
+ [spec_b],
219
+ ])
220
+
221
+ actual = subject
222
+
223
+ expect(actual.stdout).to include('ERROR -- : [knapsack_pro] You forgot to call KnapsackPro::Adapters::RSpecAdapter.bind method in your test runner configuration file. It is needed to record test files time execution. Please follow the installation guide to configure your project properly https://knapsackpro.com/perma/ruby/installation-guide')
224
+
225
+ expect(actual.exit_code).to eq 1
226
+ end
227
+ end
228
+
229
+ context 'when RSpec options are not set' do
230
+ before do
231
+ ENV['KNAPSACK_PRO_LOG_LEVEL'] = 'info'
232
+ end
233
+
234
+ after do
235
+ ENV.delete('KNAPSACK_PRO_LOG_LEVEL')
236
+ end
237
+
238
+ it 'uses a default progress formatter' do
239
+ rspec_options = ''
240
+
241
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
242
+ describe 'A_describe' do
243
+ it {}
244
+ it {}
245
+ it {}
246
+ end
247
+ SPEC
248
+
249
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
250
+ describe 'B_describe' do
251
+ it {}
252
+ it {}
253
+ end
254
+ SPEC
255
+
256
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
257
+ describe 'C_describe' do
258
+ it {}
259
+ it {}
260
+ it {}
261
+ end
262
+ SPEC
263
+
264
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
265
+ [spec_a, spec_b],
266
+ [spec_c],
267
+ ])
268
+
269
+ actual = subject
270
+
271
+ beginning_of_knapsack_pro_log_info_message = 'I, ['
272
+
273
+ # shows dots for the 1st batch of tests
274
+ expect(actual.stdout).to include('.....' + beginning_of_knapsack_pro_log_info_message)
275
+ # shows dots for the 2nd batch of tests
276
+ expect(actual.stdout).to include('...' + beginning_of_knapsack_pro_log_info_message)
277
+
278
+ expect(actual.exit_code).to eq 0
279
+ end
280
+ end
281
+
282
+ context 'when RSpec options are not set AND Knapsack Pro log level is warn' do
283
+ before do
284
+ ENV['KNAPSACK_PRO_LOG_LEVEL'] = 'warn'
285
+ ENV.delete('TEST__SHOW_DEBUG_LOG')
286
+ end
287
+ after do
288
+ ENV.delete('KNAPSACK_PRO_LOG_LEVEL')
289
+ end
290
+
291
+ it 'uses a default progress formatter AND shows dots for all test examples' do
292
+ rspec_options = ''
293
+
294
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
295
+ describe 'A_describe' do
296
+ it {}
297
+ it {}
298
+ it {}
299
+ end
300
+ SPEC
301
+
302
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
303
+ describe 'B_describe' do
304
+ it {}
305
+ it {}
306
+ end
307
+ SPEC
308
+
309
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
310
+ describe 'C_describe' do
311
+ it {}
312
+ it {}
313
+ it {}
314
+ end
315
+ SPEC
316
+
317
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
318
+ [spec_a, spec_b],
319
+ [spec_c],
320
+ ])
321
+
322
+ actual = subject
323
+
324
+ expect(actual.stdout).to include('.'*8)
325
+
326
+ expect(actual.exit_code).to eq 0
327
+ end
328
+ end
329
+
330
+ context 'when hooks are defined' do
331
+ it 'calls RSpec before/after hooks only once for multiple batches of tests' do
332
+ rspec_options = ''
333
+
334
+ spec_helper = <<~SPEC
335
+ require 'knapsack_pro'
336
+ KnapsackPro::Adapters::RSpecAdapter.bind
337
+
338
+ RSpec.configure do |config|
339
+ config.before(:suite) do
340
+ puts 'RSpec_before_suite_hook'
341
+ end
342
+ config.after(:suite) do
343
+ puts 'RSpec_after_suite_hook'
344
+ end
345
+ end
346
+ SPEC
347
+
348
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
349
+ describe 'A_describe' do
350
+ it 'A1 test example' do
351
+ expect(1).to eq 1
352
+ end
353
+ end
354
+ SPEC
355
+
356
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
357
+ describe 'B_describe' do
358
+ it 'B1 test example' do
359
+ expect(1).to eq 1
360
+ end
361
+ end
362
+ SPEC
363
+
364
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
365
+ describe 'C_describe' do
366
+ it 'C1 test example' do
367
+ expect(1).to eq 1
368
+ end
369
+ end
370
+ SPEC
371
+
372
+ generate_specs(spec_helper, rspec_options, [
373
+ [spec_a, spec_b],
374
+ [spec_c],
375
+ ])
376
+
377
+ actual = subject
378
+
379
+ expect(actual.stdout.scan(/RSpec_before_suite_hook/).size).to eq 1
380
+ expect(actual.stdout.scan(/RSpec_after_suite_hook/).size).to eq 1
381
+
382
+ expect(actual.exit_code).to eq 0
383
+ end
384
+
385
+ it 'calls queue hooks for multiple batches of tests (queue hooks can be defined multiple times)' do
386
+ rspec_options = ''
387
+
388
+ spec_helper = <<~SPEC
389
+ require 'knapsack_pro'
390
+ KnapsackPro::Adapters::RSpecAdapter.bind
391
+
392
+ KnapsackPro::Hooks::Queue.before_queue do |queue_id|
393
+ puts '1st before_queue - run before the test suite'
394
+ end
395
+ KnapsackPro::Hooks::Queue.before_queue do |queue_id|
396
+ puts '2nd before_queue - run before the test suite'
397
+ end
398
+
399
+ KnapsackPro::Hooks::Queue.before_subset_queue do |queue_id, subset_queue_id|
400
+ puts '1st before_subset_queue - run before the next subset of tests'
401
+ end
402
+ KnapsackPro::Hooks::Queue.before_subset_queue do |queue_id, subset_queue_id|
403
+ puts '2nd before_subset_queue - run before the next subset of tests'
404
+ end
405
+
406
+ KnapsackPro::Hooks::Queue.after_subset_queue do |queue_id, subset_queue_id|
407
+ puts '1st after_subset_queue - run after the previous subset of tests'
408
+ end
409
+ KnapsackPro::Hooks::Queue.after_subset_queue do |queue_id, subset_queue_id|
410
+ puts '2nd after_subset_queue - run after the previous subset of tests'
411
+ end
412
+
413
+ KnapsackPro::Hooks::Queue.after_queue do |queue_id|
414
+ puts '1st after_queue - run after the test suite'
415
+ end
416
+ KnapsackPro::Hooks::Queue.after_queue do |queue_id|
417
+ puts '2nd after_queue - run after the test suite'
418
+ end
419
+ SPEC
420
+
421
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
422
+ describe 'A_describe' do
423
+ it 'A1 test example' do
424
+ expect(1).to eq 1
425
+ end
426
+ end
427
+ SPEC
428
+
429
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
430
+ describe 'B_describe' do
431
+ it 'B1 test example' do
432
+ expect(1).to eq 1
433
+ end
434
+ end
435
+ SPEC
436
+
437
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
438
+ describe 'C_describe' do
439
+ it 'C1 test example' do
440
+ expect(1).to eq 1
441
+ end
442
+ end
443
+ SPEC
444
+
445
+ generate_specs(spec_helper, rspec_options, [
446
+ [spec_a, spec_b],
447
+ [spec_c],
448
+ ])
449
+
450
+ actual = subject
451
+
452
+ expect(actual.stdout.scan(/1st before_queue - run before the test suite/).size).to eq 1
453
+ expect(actual.stdout.scan(/2nd before_queue - run before the test suite/).size).to eq 1
454
+ expect(actual.stdout.scan(/1st before_subset_queue - run before the next subset of tests/).size).to eq 2
455
+ expect(actual.stdout.scan(/2nd before_subset_queue - run before the next subset of tests/).size).to eq 2
456
+ expect(actual.stdout.scan(/1st after_subset_queue - run after the previous subset of tests/).size).to eq 2
457
+ expect(actual.stdout.scan(/2nd after_subset_queue - run after the previous subset of tests/).size).to eq 2
458
+ expect(actual.stdout.scan(/1st after_queue - run after the test suite/).size).to eq 1
459
+ expect(actual.stdout.scan(/2nd after_queue - run after the test suite/).size).to eq 1
460
+
461
+ expect(actual.exit_code).to eq 0
462
+ end
463
+
464
+ it 'calls hooks defined with when_first_matching_example_defined only once for multiple batches of tests' do
465
+ rspec_options = '--format documentation'
466
+
467
+ spec_helper = <<~SPEC
468
+ require 'knapsack_pro'
469
+ KnapsackPro::Adapters::RSpecAdapter.bind
470
+
471
+ def when_first_matching_example_defined(type:)
472
+ env_var_name = "WHEN_FIRST_MATCHING_EXAMPLE_DEFINED_FOR_" + type.to_s.upcase
473
+
474
+ RSpec.configure do |config|
475
+ config.when_first_matching_example_defined(type: type) do
476
+ config.before(:context) do
477
+ unless ENV[env_var_name]
478
+ yield
479
+ end
480
+ ENV[env_var_name] = 'hook_called'
481
+ end
482
+ end
483
+ end
484
+ end
485
+
486
+ when_first_matching_example_defined(type: :model) do
487
+ puts 'RSpec_custom_hook_called_once_for_model'
488
+ end
489
+
490
+ when_first_matching_example_defined(type: :system) do
491
+ puts 'RSpec_custom_hook_called_once_for_system'
492
+ end
493
+
494
+ RSpec.configure do |config|
495
+ config.before(:suite) do
496
+ puts 'RSpec_before_suite_hook'
497
+ end
498
+
499
+ config.when_first_matching_example_defined(type: :model) do
500
+ config.before(:suite) do
501
+ puts 'RSpec_before_suite_hook_for_model'
502
+ end
503
+ end
504
+
505
+ config.when_first_matching_example_defined(type: :system) do
506
+ config.before(:suite) do
507
+ puts 'RSpec_before_suite_hook_for_system'
508
+ end
509
+ end
510
+ end
511
+ SPEC
512
+
513
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
514
+ describe 'A_describe', type: :model do
515
+ it 'A1 test example' do
516
+ expect(1).to eq 1
517
+ end
518
+ end
519
+ SPEC
520
+
521
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
522
+ describe 'B_describe', type: :system do
523
+ it 'B1 test example' do
524
+ expect(1).to eq 1
525
+ end
526
+ end
527
+ SPEC
528
+
529
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
530
+ describe 'C_describe' do
531
+ it 'C1 test example' do
532
+ expect(1).to eq 1
533
+ end
534
+
535
+ it 'C1 test example', :model do
536
+ expect(1).to eq 1
537
+ end
538
+ end
539
+ SPEC
540
+
541
+ spec_d = Spec.new('d_spec.rb', <<~SPEC)
542
+ describe 'D_describe', type: :system do
543
+ it 'D1 test example' do
544
+ expect(1).to eq 1
545
+ end
546
+ end
547
+ SPEC
548
+
549
+ generate_specs(spec_helper, rspec_options, [
550
+ [spec_a],
551
+ [spec_b],
552
+ [spec_c],
553
+ [spec_d],
554
+ ])
555
+
556
+ actual = subject
557
+
558
+ expect(actual.stdout.scan(/RSpec_before_suite_hook/).size).to eq 1
559
+
560
+ # skips before(:suite) hooks that were defined too late in 1st & 2nd batch of tests after before(:suite) hook is already executed
561
+ expect(actual.stdout.scan(/RSpec_before_suite_hook_for_model/).size).to eq 0
562
+ expect(actual.stdout.scan(/RSpec_before_suite_hook_for_system/).size).to eq 0
563
+
564
+ expect(actual.stdout.scan(/RSpec_custom_hook_called_once_for_model/).size).to eq 1
565
+ expect(actual.stdout.scan(/RSpec_custom_hook_called_once_for_system/).size).to eq 1
566
+
567
+ expect(actual.exit_code).to eq 0
568
+ end
569
+ end
570
+
571
+ context 'when the RSpec seed is used' do
572
+ it do
573
+ rspec_options = '--order rand:123'
574
+
575
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
576
+ describe 'A_describe' do
577
+ it 'A1 test example' do
578
+ expect(1).to eq 1
579
+ end
580
+ end
581
+ SPEC
582
+
583
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
584
+ describe 'B_describe' do
585
+ it 'B1 test example' do
586
+ expect(1).to eq 1
587
+ end
588
+ end
589
+ SPEC
590
+
591
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
592
+ describe 'C_describe' do
593
+ it 'C1 test example' do
594
+ expect(1).to eq 1
595
+ end
596
+ end
597
+ SPEC
598
+
599
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
600
+ [spec_a, spec_b],
601
+ [spec_c],
602
+ ])
603
+
604
+ actual = subject
605
+
606
+ expect(actual.stdout).to include('Randomized with seed 123')
607
+
608
+ # 1st batch
609
+ expect(actual.stdout).to include('INFO -- : [knapsack_pro] bundle exec rspec --order rand:123 --format progress --default-path spec_integration "spec_integration/a_spec.rb" "spec_integration/b_spec.rb"')
610
+ # 2nd batch
611
+ expect(actual.stdout).to include('INFO -- : [knapsack_pro] bundle exec rspec --order rand:123 --format progress --default-path spec_integration "spec_integration/c_spec.rb"')
612
+
613
+ # the final RSpec command with seed
614
+ expect(actual.stdout).to include('INFO -- : [knapsack_pro] bundle exec rspec --order rand:123 --format progress --default-path spec_integration "spec_integration/a_spec.rb" "spec_integration/b_spec.rb" "spec_integration/c_spec.rb"')
615
+
616
+ expect(actual.exit_code).to eq 0
617
+ end
618
+ end
619
+
620
+ context 'when a failing test in a batch of tests that is not the last batch fetched from the Queue API' do
621
+ it 'returns 1 as exit code (it remembers that one of the batches has a failing test)' do
622
+ rspec_options = '--format documentation'
623
+
624
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
625
+ describe 'A_describe' do
626
+ it 'A1 test example' do
627
+ expect(1).to eq 1
628
+ end
629
+ end
630
+ SPEC
631
+
632
+ failing_spec = Spec.new('failing_spec.rb', <<~SPEC)
633
+ describe 'B_describe' do
634
+ it 'B1 test example' do
635
+ expect(1).to eq 0
636
+ end
637
+ end
638
+ SPEC
639
+
640
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
641
+ describe 'C_describe' do
642
+ it 'C1 test example' do
643
+ expect(1).to eq 1
644
+ end
645
+ end
646
+ SPEC
647
+
648
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
649
+ [spec_a, failing_spec],
650
+ [spec_c],
651
+ ])
652
+
653
+ actual = subject
654
+
655
+ expect(actual.stdout).to include('B1 test example (FAILED - 1)')
656
+ expect(actual.stdout).to include('Failure/Error: expect(1).to eq 0')
657
+ expect(actual.stdout).to include('3 examples, 1 failure')
658
+
659
+ expect(actual.exit_code).to eq 1
660
+ end
661
+ end
662
+
663
+ context 'when a failing test raises an exception' do
664
+ it 'returns 1 as exit code AND the exception does not leak outside of the RSpec runner context' do
665
+ rspec_options = '--format documentation'
666
+
667
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
668
+ describe 'A_describe' do
669
+ it 'A1 test example' do
670
+ expect(1).to eq 1
671
+ end
672
+ end
673
+ SPEC
674
+
675
+ failing_spec = Spec.new('failing_spec.rb', <<~SPEC)
676
+ describe 'B_describe' do
677
+ it 'B1 test example' do
678
+ raise 'A custom exception from a test'
679
+ end
680
+ end
681
+ SPEC
682
+
683
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
684
+ describe 'C_describe' do
685
+ it 'C1 test example' do
686
+ expect(1).to eq 1
687
+ end
688
+ end
689
+ SPEC
690
+
691
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
692
+ [spec_a, failing_spec],
693
+ [spec_c],
694
+ ])
695
+
696
+ actual = subject
697
+
698
+ expect(actual.stdout).to include('B1 test example (FAILED - 1)')
699
+ expect(actual.stdout).to include("Failure/Error: raise 'A custom exception from a test'")
700
+ expect(actual.stdout).to include('3 examples, 1 failure')
701
+
702
+ expect(actual.exit_code).to eq 1
703
+ end
704
+ end
705
+
706
+ context 'when a spec file has a syntax error outside of the test example' do
707
+ it 'stops running tests on the batch that has a test file with the syntax error AND returns 1 as exit code' do
708
+ rspec_options = '--format documentation'
709
+
710
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
711
+ describe 'A_describe' do
712
+ it 'A1 test example' do
713
+ expect(1).to eq 1
714
+ end
715
+ end
716
+ SPEC
717
+
718
+ failing_spec = Spec.new('failing_spec.rb', <<~SPEC)
719
+ describe 'B_describe' do
720
+ a_fake_method
721
+
722
+ it 'B1 test example' do
723
+ expect(1).to eq 1
724
+ end
725
+ end
726
+ SPEC
727
+
728
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
729
+ describe 'C_describe' do
730
+ it 'C1 test example' do
731
+ expect(1).to eq 1
732
+ end
733
+ end
734
+ SPEC
735
+
736
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
737
+ [spec_a],
738
+ [failing_spec],
739
+ [spec_c],
740
+ ])
741
+
742
+ actual = subject
743
+
744
+ # 1st batch of tests executed correctly
745
+ expect(actual.stdout).to include('A1 test example')
746
+ # 2nd batch contains the test file that cannot be loaded and the test file is not executed
747
+ expect(actual.stdout).to_not include('B1 test example')
748
+ # 3rd batch is never executed
749
+ expect(actual.stdout).to_not include('C1 test example')
750
+
751
+ expect(actual.stdout).to include('An error occurred while loading ./spec_integration/failing_spec.rb')
752
+ expect(actual.stdout).to match(/undefined local variable or method `a_fake_method' for.* RSpec::ExampleGroups::BDescribe/)
753
+ expect(actual.stdout).to include('WARN -- : [knapsack_pro] RSpec wants to quit')
754
+ expect(actual.stdout).to include('1 example, 0 failures, 1 error occurred outside of examples')
755
+
756
+ expect(actual.exit_code).to eq 1
757
+ end
758
+ end
759
+
760
+ context 'when a syntax error (an exception) in spec_helper.rb' do
761
+ it 'exits early with 1 as the exit code without running tests because RSpec wants to quit' do
762
+ rspec_options = '--format documentation'
763
+
764
+ spec_helper = <<~SPEC
765
+ require 'knapsack_pro'
766
+ KnapsackPro::Adapters::RSpecAdapter.bind
767
+
768
+ a_fake_method
769
+ SPEC
770
+
771
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
772
+ describe 'A_describe' do
773
+ it 'A1 test example' do
774
+ expect(1).to eq 1
775
+ end
776
+ end
777
+ SPEC
778
+
779
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
780
+ describe 'B_describe' do
781
+ it 'B1 test example' do
782
+ expect(1).to eq 1
783
+ end
784
+ end
785
+ SPEC
786
+
787
+ generate_specs(spec_helper, rspec_options, [
788
+ [spec_a],
789
+ [spec_b],
790
+ ])
791
+
792
+ actual = subject
793
+
794
+ expect(actual.stdout).to include('An error occurred while loading spec_helper.')
795
+ expect(actual.stdout).to include("undefined local variable or method `a_fake_method' for main")
796
+ expect(actual.stdout).to include('0 examples, 0 failures, 1 error occurred outside of examples')
797
+
798
+ expect(actual.exit_code).to eq 1
799
+ end
800
+ end
801
+
802
+ # Based on:
803
+ # https://github.com/rspec/rspec-core/pull/2926/files
804
+ context 'when RSpec is quitting' do
805
+ let(:helper_with_exit_location) { "#{SPEC_DIRECTORY}/helper_with_exit.rb" }
806
+
807
+ it 'returns non zero exit code because RSpec is quitting' do
808
+ skip 'Not supported by this RSpec version' if RSpec::Core::Version::STRING == '3.10.2'
809
+
810
+ File.open(helper_with_exit_location, 'w') { |file| file.write('exit 123') }
811
+
812
+ rspec_options = "--format documentation --require ./#{helper_with_exit_location}"
813
+
814
+ spec_helper = <<~SPEC
815
+ require 'knapsack_pro'
816
+ KnapsackPro::Adapters::RSpecAdapter.bind
817
+ SPEC
818
+
819
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
820
+ describe 'A_describe' do
821
+ it 'A1 test example' do
822
+ expect(1).to eq 1
823
+ end
824
+ end
825
+ SPEC
826
+
827
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
828
+ describe 'B_describe' do
829
+ it 'B1 test example' do
830
+ expect(1).to eq 1
831
+ end
832
+ end
833
+ SPEC
834
+
835
+ generate_specs(spec_helper, rspec_options, [
836
+ [spec_a],
837
+ [spec_b],
838
+ ])
839
+
840
+ actual = subject
841
+
842
+ expect(actual.stdout).to include('While loading ./spec_integration/helper_with_exit.rb an `exit` / `raise SystemExit` occurred, RSpec will now quit.')
843
+
844
+ expect(actual.stdout).to_not include('A1 test example')
845
+ expect(actual.stdout).to_not include('B1 test example')
846
+
847
+ expect(actual.exit_code).to eq 123
848
+ end
849
+ end
850
+
851
+ context 'when the test suite has pending tests' do
852
+ it 'shows the summary of pending tests' do
853
+ rspec_options = '--format documentation'
854
+
855
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
856
+ describe 'A_describe' do
857
+ it 'A1 test example' do
858
+ expect(1).to eq 1
859
+ end
860
+ end
861
+ SPEC
862
+
863
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
864
+ describe 'B_describe' do
865
+ xit 'B1 test example' do
866
+ expect(1).to eq 1
867
+ end
868
+ end
869
+ SPEC
870
+
871
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
872
+ describe 'C_describe' do
873
+ it 'C1 test example' do
874
+ expect(1).to eq 1
875
+ end
876
+
877
+ xit 'C2 test example' do
878
+ expect(1).to eq 1
879
+ end
880
+ end
881
+ SPEC
882
+
883
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
884
+ [spec_a, spec_b],
885
+ [spec_c],
886
+ ])
887
+
888
+ actual = subject
889
+
890
+ expect(actual.stdout).to include('B1 test example (PENDING: Temporarily skipped with xit)')
891
+ expect(actual.stdout).to include('C2 test example (PENDING: Temporarily skipped with xit)')
892
+
893
+ expect(actual.stdout).to include("Pending: (Failures listed here are expected and do not affect your suite's status)")
894
+ expect(actual.stdout).to include('1) B_describe B1 test example')
895
+ expect(actual.stdout).to include('2) C_describe C2 test example')
896
+
897
+ expect(actual.stdout).to include('4 examples, 0 failures, 2 pending')
898
+
899
+ expect(actual.exit_code).to eq 0
900
+ end
901
+ end
902
+
903
+ context 'when a test file raises an exception that cannot be handle by RSpec' do
904
+ it 'stops running tests when unhandled exception happens AND sets 1 as exit code AND shows summary of unexecuted tests' do
905
+ rspec_options = '--format documentation'
906
+
907
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
908
+ describe 'A_describe' do
909
+ it 'A1 test example' do
910
+ expect(1).to eq 1
911
+ end
912
+ end
913
+ SPEC
914
+
915
+ # list of unhandled exceptions:
916
+ # RSpec::Support::AllExceptionsExceptOnesWeMustNotRescue::AVOID_RESCUING
917
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
918
+ describe 'B_describe' do
919
+ it 'B1 test example' do
920
+ raise NoMemoryError.new
921
+ end
922
+ end
923
+ SPEC
924
+
925
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
926
+ describe 'C_describe' do
927
+ it 'C1 test example' do
928
+ expect(1).to eq 1
929
+ end
930
+ end
931
+ SPEC
932
+
933
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
934
+ [spec_a],
935
+ [spec_b],
936
+ [spec_c],
937
+ ])
938
+
939
+ actual = subject
940
+
941
+ expect(actual.stdout).to include('A1 test example')
942
+
943
+ expect(actual.stdout).to include('B_describe')
944
+ expect(actual.stdout).to include('An unexpected exception happened. RSpec cannot handle it. The exception: #<NoMemoryError: NoMemoryError>')
945
+ expect(actual.stdout).to_not include('B1 test example')
946
+
947
+ expect(actual.stdout).to_not include('C1 test example')
948
+
949
+ # 2nd test example raised unhandled exception during runtime.
950
+ # It breaks RSpec so it was not marked as failed.
951
+ expect(actual.stdout).to include('2 examples, 0 failures')
952
+
953
+ expect(actual.stdout).to include('WARN -- : [knapsack_pro] Unexecuted tests on this CI node (including pending tests): spec_integration/b_spec.rb')
954
+
955
+ expect(actual.exit_code).to eq 1
956
+ end
957
+ end
958
+
959
+ context 'when a test file raises an exception that cannot be handle by RSpec AND --error-exit-code is set' do
960
+ it 'sets a custom exit code' do
961
+ rspec_options = '--format documentation --error-exit-code 2'
962
+
963
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
964
+ describe 'A_describe' do
965
+ it 'A1 test example' do
966
+ expect(1).to eq 1
967
+ end
968
+ end
969
+ SPEC
970
+
971
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
972
+ describe 'B_describe' do
973
+ it 'B1 test example' do
974
+ raise NoMemoryError.new
975
+ end
976
+ end
977
+ SPEC
978
+
979
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
980
+ describe 'C_describe' do
981
+ it 'C1 test example' do
982
+ expect(1).to eq 1
983
+ end
984
+ end
985
+ SPEC
986
+
987
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
988
+ [spec_a],
989
+ [spec_b],
990
+ [spec_c],
991
+ ])
992
+
993
+ actual = subject
994
+
995
+ expect(actual.exit_code).to eq 2
996
+ end
997
+ end
998
+
999
+ context 'when a termination signal is received by the process' do
1000
+ it 'terminates the process after tests from the current RSpec ExampleGroup are executed and sets 1 as exit code' do
1001
+ rspec_options = '--format documentation'
1002
+
1003
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1004
+ describe 'A_describe' do
1005
+ it 'A1 test example' do
1006
+ expect(1).to eq 1
1007
+ end
1008
+ end
1009
+ SPEC
1010
+
1011
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1012
+ describe 'B1_describe' do
1013
+ describe 'B1.1_describe' do
1014
+ xit 'B1.1.1 test example' do
1015
+ expect(1).to eq 1
1016
+ end
1017
+ it 'B1.1.2 test example' do
1018
+ Process.kill("INT", Process.pid)
1019
+ end
1020
+ it 'B1.1.3 test example' do
1021
+ expect(1).to eq 0
1022
+ end
1023
+ end
1024
+
1025
+ describe 'B1.2_describe' do
1026
+ it 'B1.2.1 test example' do
1027
+ expect(1).to eq 1
1028
+ end
1029
+ end
1030
+ end
1031
+
1032
+ describe 'B2_describe' do
1033
+ it 'B2.1 test example' do
1034
+ expect(1).to eq 1
1035
+ end
1036
+ end
1037
+ SPEC
1038
+
1039
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1040
+ describe 'C_describe' do
1041
+ it 'C1 test example' do
1042
+ expect(1).to eq 1
1043
+ end
1044
+ end
1045
+ SPEC
1046
+
1047
+ spec_d = Spec.new('d_spec.rb', <<~SPEC)
1048
+ describe 'D_describe' do
1049
+ it 'D1 test example' do
1050
+ expect(1).to eq 1
1051
+ end
1052
+ end
1053
+ SPEC
1054
+
1055
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1056
+ [spec_a],
1057
+ [spec_b, spec_c],
1058
+ [spec_d],
1059
+ ])
1060
+
1061
+ actual = subject
1062
+
1063
+ expect(actual.stdout).to include('B1.1.1 test example (PENDING: Temporarily skipped with xit)')
1064
+ expect(actual.stdout).to include('INT signal has been received. Terminating Knapsack Pro...')
1065
+ expect(actual.stdout).to include('B1.1.2 test example')
1066
+ expect(actual.stdout).to include('B1.1.3 test example (FAILED - 1)')
1067
+ expect(actual.stdout).to include('B1.2.1 test example')
1068
+
1069
+ # next ExampleGroup within the same b_spec.rb is not executed
1070
+ expect(actual.stdout).to_not include('B2.1 test example')
1071
+
1072
+ # next test file from the same batch is not executed
1073
+ expect(actual.stdout).to_not include('C1 test example')
1074
+
1075
+ # next batch of tests is not pulled from the Queue API and is not executed
1076
+ expect(actual.stdout).to_not include('D1 test example')
1077
+
1078
+
1079
+ expect(actual.stdout).to include(
1080
+ <<~OUTPUT
1081
+ Pending: (Failures listed here are expected and do not affect your suite's status)
1082
+
1083
+ 1) B1_describe B1.1_describe B1.1.1 test example
1084
+ OUTPUT
1085
+ )
1086
+
1087
+ expect(actual.stdout).to include(
1088
+ <<~OUTPUT
1089
+ Failures:
1090
+
1091
+ 1) B1_describe B1.1_describe B1.1.3 test example
1092
+ OUTPUT
1093
+ )
1094
+
1095
+ expect(actual.exit_code).to eq 1
1096
+ end
1097
+ end
1098
+
1099
+ context 'when a termination signal is received by the process AND --error-exit-code is set' do
1100
+ it 'terminates the process AND sets a custom exit code' do
1101
+ rspec_options = '--format documentation --error-exit-code 3'
1102
+
1103
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1104
+ describe 'A_describe' do
1105
+ it 'A1 test example' do
1106
+ expect(1).to eq 1
1107
+ end
1108
+ end
1109
+ SPEC
1110
+
1111
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1112
+ describe 'B_describe' do
1113
+ it 'B1 test example' do
1114
+ Process.kill("INT", Process.pid)
1115
+ end
1116
+ end
1117
+ SPEC
1118
+
1119
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1120
+ describe 'C_describe' do
1121
+ it 'C1 test example' do
1122
+ expect(1).to eq 1
1123
+ end
1124
+ end
1125
+ SPEC
1126
+
1127
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1128
+ [spec_a],
1129
+ [spec_b],
1130
+ [spec_c],
1131
+ ])
1132
+
1133
+ actual = subject
1134
+
1135
+ expect(actual.stdout).to include('INT signal has been received. Terminating Knapsack Pro...')
1136
+
1137
+ expect(actual.exit_code).to eq 3
1138
+ end
1139
+ end
1140
+
1141
+ context 'when deprecated run_all_when_everything_filtered option is true' do
1142
+ it 'shows an error message AND sets 1 as exit code' do
1143
+ rspec_options = '--format documentation'
1144
+
1145
+ spec_helper = <<~SPEC
1146
+ require 'knapsack_pro'
1147
+ KnapsackPro::Adapters::RSpecAdapter.bind
1148
+
1149
+ RSpec.configure do |config|
1150
+ config.run_all_when_everything_filtered = true
1151
+ end
1152
+ SPEC
1153
+
1154
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1155
+ describe 'A_describe' do
1156
+ it 'A1 test example' do
1157
+ expect(1).to eq 1
1158
+ end
1159
+ end
1160
+ SPEC
1161
+
1162
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1163
+ describe 'B_describe' do
1164
+ it 'B1 test example' do
1165
+ expect(1).to eq 1
1166
+ end
1167
+ end
1168
+ SPEC
1169
+
1170
+ generate_specs(spec_helper, rspec_options, [
1171
+ [spec_a],
1172
+ [spec_b],
1173
+ ])
1174
+
1175
+ actual = subject
1176
+
1177
+ expect(actual.stdout).to include('ERROR -- : [knapsack_pro] The run_all_when_everything_filtered option is deprecated. See: https://knapsackpro.com/perma/ruby/rspec-deprecated-run-all-when-everything-filtered')
1178
+
1179
+ expect(actual.stdout).to_not include('A1 test example')
1180
+ expect(actual.stdout).to_not include('B1 test example')
1181
+
1182
+ expect(actual.exit_code).to eq 1
1183
+ end
1184
+ end
1185
+
1186
+ context 'when filter_run_when_matching is set to :focus and some tests are tagged with the focus tag' do
1187
+ it 'shows an error message for :focus tagged tests AND sets 1 as exit code (shows the error because the batch of tests that has no focus tagged tests will run tests instead of not running them)' do
1188
+ rspec_options = '--format documentation'
1189
+
1190
+ spec_helper = <<~SPEC
1191
+ require 'knapsack_pro'
1192
+ KnapsackPro::Adapters::RSpecAdapter.bind
1193
+
1194
+ RSpec.configure do |config|
1195
+ config.filter_run_when_matching :focus
1196
+ end
1197
+ SPEC
1198
+
1199
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1200
+ describe 'A_describe' do
1201
+ it 'A1 test example' do
1202
+ expect(1).to eq 1
1203
+ end
1204
+ end
1205
+ SPEC
1206
+
1207
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1208
+ describe 'B_describe' do
1209
+ it 'B1 test example', :focus do
1210
+ expect(1).to eq 1
1211
+ end
1212
+ it 'B2 test example' do
1213
+ expect(1).to eq 1
1214
+ end
1215
+ end
1216
+ SPEC
1217
+
1218
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1219
+ describe 'C_describe' do
1220
+ it 'C1 test example' do
1221
+ expect(1).to eq 1
1222
+ end
1223
+ end
1224
+ SPEC
1225
+
1226
+ generate_specs(spec_helper, rspec_options, [
1227
+ [spec_a],
1228
+ [spec_b],
1229
+ [spec_c],
1230
+ ])
1231
+
1232
+ actual = subject
1233
+
1234
+ expect(actual.stdout).to include('A1 test example')
1235
+
1236
+ expect(actual.stdout).to include('B1 test example (FAILED - 1)')
1237
+ expect(actual.stdout).to_not include('B2 test example') # skips B2 test due to tagged B1
1238
+
1239
+ expect(actual.stdout).to include('C1 test example')
1240
+
1241
+ expect(actual.stdout).to include('Knapsack Pro found an example tagged with focus in spec_integration/b_spec.rb, please remove it. See more: https://knapsackpro.com/perma/ruby/rspec-skips-tests')
1242
+
1243
+ expect(actual.exit_code).to eq 1
1244
+ end
1245
+ end
1246
+
1247
+ context 'when the late CI node has an empty batch of tests because other CI nodes already consumed tests from the Queue API' do
1248
+ it 'sets 0 as exit code' do
1249
+ rspec_options = '--format documentation'
1250
+
1251
+ generate_specs(spec_helper_with_knapsack, rspec_options, [])
1252
+
1253
+ actual = subject
1254
+
1255
+ expect(actual.stdout).to include('0 examples, 0 failures')
1256
+ expect(actual.stdout).to include('WARN -- : [knapsack_pro] No test files were executed on this CI node.')
1257
+ expect(actual.stdout).to include('DEBUG -- : [knapsack_pro] This CI node likely started work late after the test files were already executed by other CI nodes consuming the queue.')
1258
+
1259
+ expect(actual.exit_code).to eq 0
1260
+ end
1261
+ end
1262
+
1263
+ context 'when the fail_if_no_examples option is true AND the late CI node has an empty batch of tests because other CI nodes already consumed tests from the Queue API' do
1264
+ it 'sets 0 as exit code to ignore the fail_if_no_examples option' do
1265
+ rspec_options = '--format documentation'
1266
+
1267
+ spec_helper = <<~SPEC
1268
+ require 'knapsack_pro'
1269
+ KnapsackPro::Adapters::RSpecAdapter.bind
1270
+
1271
+ RSpec.configure do |config|
1272
+ config.fail_if_no_examples = true
1273
+ end
1274
+ SPEC
1275
+
1276
+ generate_specs(spec_helper, rspec_options, [])
1277
+
1278
+ actual = subject
1279
+
1280
+ expect(actual.stdout).to include('0 examples, 0 failures')
1281
+ expect(actual.stdout).to include('WARN -- : [knapsack_pro] No test files were executed on this CI node.')
1282
+ expect(actual.stdout).to include('DEBUG -- : [knapsack_pro] This CI node likely started work late after the test files were already executed by other CI nodes consuming the queue.')
1283
+
1284
+ expect(actual.exit_code).to eq 0
1285
+ end
1286
+ end
1287
+
1288
+ context 'when the fail_if_no_examples option is true AND a batch of tests has a test file without test examples' do
1289
+ it 'sets 0 as exit code to ignore the fail_if_no_examples option' do
1290
+ rspec_options = '--format documentation'
1291
+
1292
+ spec_helper = <<~SPEC
1293
+ require 'knapsack_pro'
1294
+ KnapsackPro::Adapters::RSpecAdapter.bind
1295
+
1296
+ RSpec.configure do |config|
1297
+ config.fail_if_no_examples = true
1298
+ end
1299
+ SPEC
1300
+
1301
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1302
+ describe 'A_describe' do
1303
+ it 'A1 test example' do
1304
+ expect(1).to eq 1
1305
+ end
1306
+ end
1307
+ SPEC
1308
+
1309
+ spec_b_with_no_examples = Spec.new('b_spec.rb', <<~SPEC)
1310
+ describe 'B_describe' do
1311
+ end
1312
+ SPEC
1313
+
1314
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1315
+ describe 'C_describe' do
1316
+ it 'C1 test example' do
1317
+ expect(1).to eq 1
1318
+ end
1319
+ end
1320
+ SPEC
1321
+
1322
+ generate_specs(spec_helper, rspec_options, [
1323
+ [spec_a],
1324
+ [spec_b_with_no_examples],
1325
+ [spec_c],
1326
+ ])
1327
+
1328
+ actual = subject
1329
+
1330
+ expect(actual.stdout).to include('2 examples, 0 failures')
1331
+
1332
+ expect(actual.exit_code).to eq 0
1333
+ end
1334
+ end
1335
+
1336
+ context 'when tests are failing AND --failure-exit-code is set' do
1337
+ it 'returns a custom exit code' do
1338
+ rspec_options = '--format documentation --failure-exit-code 4'
1339
+
1340
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1341
+ describe 'A_describe' do
1342
+ it 'A1 test example' do
1343
+ expect(1).to eq 1
1344
+ end
1345
+ end
1346
+ SPEC
1347
+
1348
+ failing_spec = Spec.new('failing_spec.rb', <<~SPEC)
1349
+ describe 'B_describe' do
1350
+ it 'B1 test example' do
1351
+ expect(1).to eq 0
1352
+ end
1353
+ end
1354
+ SPEC
1355
+
1356
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1357
+ describe 'C_describe' do
1358
+ it 'C1 test example' do
1359
+ expect(1).to eq 1
1360
+ end
1361
+ end
1362
+ SPEC
1363
+
1364
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1365
+ [spec_a, failing_spec],
1366
+ [spec_c],
1367
+ ])
1368
+
1369
+ actual = subject
1370
+
1371
+ expect(actual.stdout).to include('B1 test example (FAILED - 1)')
1372
+ expect(actual.stdout).to include('Failure/Error: expect(1).to eq 0')
1373
+ expect(actual.stdout).to include('3 examples, 1 failure')
1374
+
1375
+ expect(actual.exit_code).to eq 4
1376
+ end
1377
+ end
1378
+
1379
+ context 'when --profile is set' do
1380
+ it 'shows top slowest examples AND top slowest example groups' do
1381
+ rspec_options = '--format d --profile'
1382
+
1383
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1384
+ describe 'A_describe' do
1385
+ it 'A1 test example' do
1386
+ expect(1).to eq 1
1387
+ end
1388
+ end
1389
+ SPEC
1390
+
1391
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1392
+ describe 'B_describe' do
1393
+ it 'B1 test example' do
1394
+ expect(1).to eq 1
1395
+ end
1396
+ end
1397
+ SPEC
1398
+
1399
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1400
+ describe 'C_describe' do
1401
+ it 'C1 test example' do
1402
+ expect(1).to eq 1
1403
+ end
1404
+ end
1405
+ SPEC
1406
+
1407
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1408
+ [spec_a, spec_b],
1409
+ [spec_c],
1410
+ ])
1411
+
1412
+ actual = subject
1413
+
1414
+ expect(actual.stdout).to include('Top 3 slowest examples')
1415
+ expect(actual.stdout).to include('A_describe A1 test example')
1416
+ expect(actual.stdout).to include('B_describe B1 test example')
1417
+ expect(actual.stdout).to include('C_describe C1 test example')
1418
+
1419
+ expect(actual.stdout).to include('Top 3 slowest example groups')
1420
+
1421
+ expect(actual.exit_code).to eq 0
1422
+ end
1423
+ end
1424
+
1425
+ context 'when an invalid RSpec option is set' do
1426
+ it 'returns 1 as exit code AND shows an error message to stderr' do
1427
+ rspec_options = '--format d --fake-rspec-option'
1428
+
1429
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1430
+ describe 'A_describe' do
1431
+ it 'A1 test example' do
1432
+ expect(1).to eq 1
1433
+ end
1434
+ end
1435
+ SPEC
1436
+
1437
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1438
+ describe 'B_describe' do
1439
+ it 'B1 test example' do
1440
+ expect(1).to eq 1
1441
+ end
1442
+ end
1443
+ SPEC
1444
+
1445
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1446
+ [spec_a],
1447
+ [spec_b],
1448
+ ])
1449
+
1450
+ actual = subject
1451
+
1452
+ expect(actual.stderr).to include('invalid option: --fake-rspec-option')
1453
+
1454
+ expect(actual.exit_code).to eq 1
1455
+ end
1456
+ end
1457
+
1458
+ context 'when --fail-fast is set' do
1459
+ it 'stops running tests on the failing test AND returns 1 as exit code AND shows a warning message' do
1460
+ rspec_options = '--format d --fail-fast'
1461
+
1462
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1463
+ describe 'A_describe' do
1464
+ it 'A1 test example' do
1465
+ expect(1).to eq 1
1466
+ end
1467
+ end
1468
+ SPEC
1469
+
1470
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1471
+ describe 'B_describe' do
1472
+ it 'B1 test example' do
1473
+ expect(1).to eq 0
1474
+ end
1475
+ end
1476
+ SPEC
1477
+
1478
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1479
+ describe 'C_describe' do
1480
+ it 'C1 test example' do
1481
+ expect(1).to eq 1
1482
+ end
1483
+ it 'C2 test example' do
1484
+ expect(1).to eq 0
1485
+ end
1486
+ end
1487
+ SPEC
1488
+
1489
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1490
+ [spec_a, spec_b],
1491
+ [spec_c],
1492
+ ])
1493
+
1494
+ actual = subject
1495
+
1496
+ expect(actual.stdout).to include('A1 test example')
1497
+ expect(actual.stdout).to include('B1 test example')
1498
+ expect(actual.stdout).to_not include('C1 test example')
1499
+ expect(actual.stdout).to_not include('C2 test example')
1500
+
1501
+ expect(actual.stdout).to include('WARN -- : [knapsack_pro] Test execution has been canceled because the RSpec --fail-fast option is enabled. It will cause other CI nodes to run tests longer because they need to consume more tests from the Knapsack Pro Queue API.')
1502
+
1503
+ expect(actual.stdout).to include('2 examples, 1 failure')
1504
+
1505
+ expect(actual.exit_code).to eq 1
1506
+ end
1507
+ end
1508
+
1509
+ context 'when the fail_fast option is set with a specific number of tests' do
1510
+ it 'stops running tests on the 2nd failing test AND returns 1 as exit code AND shows a warning message when fail fast limit met' do
1511
+ rspec_options = '--format d'
1512
+
1513
+ spec_helper = <<~SPEC
1514
+ require 'knapsack_pro'
1515
+ KnapsackPro::Adapters::RSpecAdapter.bind
1516
+
1517
+ RSpec.configure do |config|
1518
+ config.fail_fast = 2
1519
+ end
1520
+ SPEC
1521
+
1522
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1523
+ describe 'A_describe' do
1524
+ it 'A1 test example' do
1525
+ expect(1).to eq 0
1526
+ end
1527
+ end
1528
+ SPEC
1529
+
1530
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1531
+ describe 'B_describe' do
1532
+ it 'B1 test example' do
1533
+ expect(1).to eq 1
1534
+ end
1535
+ it 'B2 test example' do
1536
+ expect(1).to eq 0
1537
+ end
1538
+ end
1539
+ SPEC
1540
+
1541
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1542
+ describe 'C_describe' do
1543
+ it 'C1 test example' do
1544
+ expect(1).to eq 1
1545
+ end
1546
+ it 'C2 test example' do
1547
+ expect(1).to eq 1
1548
+ end
1549
+ end
1550
+ SPEC
1551
+
1552
+ generate_specs(spec_helper, rspec_options, [
1553
+ [spec_a, spec_b],
1554
+ [spec_c],
1555
+ ])
1556
+
1557
+ actual = subject
1558
+
1559
+ expect(actual.stdout).to include('A1 test example (FAILED - 1)')
1560
+ expect(actual.stdout).to include('B1 test example')
1561
+ expect(actual.stdout).to include('B2 test example (FAILED - 2)')
1562
+ expect(actual.stdout).to_not include('C1 test example')
1563
+ expect(actual.stdout).to_not include('C2 test example')
1564
+
1565
+ expect(actual.stdout).to include('WARN -- : [knapsack_pro] Test execution has been canceled because the RSpec --fail-fast option is enabled. It will cause other CI nodes to run tests longer because they need to consume more tests from the Knapsack Pro Queue API.')
1566
+
1567
+ expect(actual.stdout).to include('3 examples, 2 failures')
1568
+
1569
+ expect(actual.exit_code).to eq 1
1570
+ end
1571
+ end
1572
+
1573
+ context 'when --tag is set' do
1574
+ it 'runs only tagged test examples from multiple batches of tests fetched from the Queue API' do
1575
+ rspec_options = '--format d --tag my_tag'
1576
+
1577
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1578
+ describe 'A_describe' do
1579
+ it 'A1 test example' do
1580
+ expect(1).to eq 1
1581
+ end
1582
+ it 'A2 test example', :my_tag do
1583
+ expect(1).to eq 1
1584
+ end
1585
+ end
1586
+ SPEC
1587
+
1588
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1589
+ describe 'B_describe', :my_tag do
1590
+ it 'B1 test example' do
1591
+ expect(1).to eq 1
1592
+ end
1593
+ end
1594
+ SPEC
1595
+
1596
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1597
+ describe 'C_describe' do
1598
+ it 'C1 test example' do
1599
+ expect(1).to eq 1
1600
+ end
1601
+ end
1602
+ SPEC
1603
+
1604
+ spec_d = Spec.new('d_spec.rb', <<~SPEC)
1605
+ describe 'D_describe' do
1606
+ it 'D1 test example' do
1607
+ expect(1).to eq 1
1608
+ end
1609
+ it 'D2 test example', :my_tag do
1610
+ expect(1).to eq 1
1611
+ end
1612
+ end
1613
+ SPEC
1614
+
1615
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1616
+ [spec_a, spec_b],
1617
+ [spec_c],
1618
+ [spec_d],
1619
+ ])
1620
+
1621
+ actual = subject
1622
+
1623
+ expect(actual.stdout).to_not include('A1 test example')
1624
+ expect(actual.stdout).to include('A2 test example')
1625
+
1626
+ expect(actual.stdout).to include('B1 test example')
1627
+
1628
+ expect(actual.stdout).to_not include('C1 test example')
1629
+
1630
+ expect(actual.stdout).to_not include('D1 test example')
1631
+ expect(actual.stdout).to include('D2 test example')
1632
+
1633
+ expect(actual.stdout).to include('3 examples, 0 failures')
1634
+
1635
+ expect(actual.exit_code).to eq 0
1636
+ end
1637
+ end
1638
+
1639
+ context 'when the RSpec split by examples is enabled' do
1640
+ before do
1641
+ ENV['KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES'] = 'true'
1642
+
1643
+ # remember to mock Queue API batches to include test examples (example: a_spec.rb[1:1])
1644
+ # for the following slow test files
1645
+ ENV['KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN'] = "#{SPEC_DIRECTORY}/a_spec.rb"
1646
+ end
1647
+ after do
1648
+ ENV.delete('KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES')
1649
+ ENV.delete('KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN')
1650
+ end
1651
+
1652
+ it 'splits slow test files by examples AND ensures the test examples are executed only once' do
1653
+ rspec_options = '--format d'
1654
+
1655
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1656
+ describe 'A_describe' do
1657
+ it 'A1 test example' do
1658
+ expect(1).to eq 1
1659
+ end
1660
+ it 'A2 test example' do
1661
+ expect(1).to eq 1
1662
+ end
1663
+ end
1664
+ SPEC
1665
+
1666
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1667
+ describe 'B_describe' do
1668
+ it 'B1 test example' do
1669
+ expect(1).to eq 1
1670
+ end
1671
+ it 'B2 test example' do
1672
+ expect(1).to eq 1
1673
+ end
1674
+ end
1675
+ SPEC
1676
+
1677
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1678
+ describe 'C_describe' do
1679
+ it 'C1 test example' do
1680
+ expect(1).to eq 1
1681
+ end
1682
+ it 'C2 test example' do
1683
+ expect(1).to eq 1
1684
+ end
1685
+ end
1686
+ SPEC
1687
+
1688
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1689
+ [spec_a, spec_b, spec_c]
1690
+ ])
1691
+ mock_test_cases_for_slow_test_files([
1692
+ "#{spec_a.path}[1:1]",
1693
+ "#{spec_a.path}[1:2]",
1694
+ ])
1695
+ stub_spec_batches([
1696
+ ["#{spec_a.path}[1:1]", spec_b.path],
1697
+ ["#{spec_a.path}[1:2]", spec_c.path],
1698
+ ])
1699
+
1700
+ actual = subject
1701
+
1702
+ expect(actual.stdout).to include('DEBUG -- : [knapsack_pro] Detected 1 slow test files: [{"path"=>"spec_integration/a_spec.rb"}]')
1703
+
1704
+ expect(actual.stdout).to include(
1705
+ <<~OUTPUT
1706
+ A_describe
1707
+ A1 test example
1708
+
1709
+ B_describe
1710
+ B1 test example
1711
+ B2 test example
1712
+ OUTPUT
1713
+ )
1714
+
1715
+ expect(actual.stdout).to include(
1716
+ <<~OUTPUT
1717
+ A_describe
1718
+ A2 test example
1719
+
1720
+ C_describe
1721
+ C1 test example
1722
+ C2 test example
1723
+ OUTPUT
1724
+ )
1725
+
1726
+ expect(actual.stdout.scan(/A1 test example/).size).to eq 1
1727
+ expect(actual.stdout.scan(/A2 test example/).size).to eq 1
1728
+
1729
+ expect(actual.stdout).to include('6 examples, 0 failures')
1730
+
1731
+ expect(actual.exit_code).to eq 0
1732
+ end
1733
+ end
1734
+
1735
+ context 'when the RSpec split by examples is enabled AND --tag is set' do
1736
+ before do
1737
+ ENV['KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES'] = 'true'
1738
+
1739
+ # remember to mock Queue API batches to include test examples (example: a_spec.rb[1:1])
1740
+ # for the following slow test files
1741
+ ENV['KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN'] = "#{SPEC_DIRECTORY}/a_spec.rb"
1742
+ end
1743
+ after do
1744
+ ENV.delete('KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES')
1745
+ ENV.delete('KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN')
1746
+ end
1747
+
1748
+ it 'sets 1 as exit code AND raises an error (a test example path as a_spec.rb[1:1] would always be executed even when it does not have the tag that is set via the --tag option. We cannot run tests because it could lead to running unintentional tests)' do
1749
+ rspec_options = '--format d --tag my_tag'
1750
+
1751
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1752
+ describe 'A_describe' do
1753
+ it 'A1 test example' do
1754
+ expect(1).to eq 1
1755
+ end
1756
+ it 'A2 test example' do
1757
+ expect(1).to eq 1
1758
+ end
1759
+ end
1760
+ SPEC
1761
+
1762
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1763
+ describe 'B_describe', :my_tag do
1764
+ it 'B1 test example' do
1765
+ expect(1).to eq 1
1766
+ end
1767
+ it 'B2 test example' do
1768
+ expect(1).to eq 1
1769
+ end
1770
+ end
1771
+ SPEC
1772
+
1773
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1774
+ describe 'C_describe' do
1775
+ it 'C1 test example' do
1776
+ expect(1).to eq 1
1777
+ end
1778
+ it 'C2 test example' do
1779
+ expect(1).to eq 1
1780
+ end
1781
+ end
1782
+ SPEC
1783
+
1784
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1785
+ [spec_a, spec_b, spec_c]
1786
+ ])
1787
+ mock_test_cases_for_slow_test_files([
1788
+ "#{spec_a.path}[1:1]",
1789
+ "#{spec_a.path}[1:2]",
1790
+ ])
1791
+ stub_spec_batches([
1792
+ ["#{spec_a.path}[1:1]", spec_b.path],
1793
+ ["#{spec_a.path}[1:2]", spec_c.path],
1794
+ ])
1795
+
1796
+ actual = subject
1797
+
1798
+ expect(actual.stdout).to include('ERROR -- : [knapsack_pro] It is not allowed to use the RSpec tag option together with the RSpec split by test examples feature. Please see: https://knapsackpro.com/perma/ruby/rspec-split-by-test-examples-tag')
1799
+
1800
+ expect(actual.stdout).to_not include('A1 test example')
1801
+ expect(actual.stdout).to_not include('A2 test example')
1802
+ expect(actual.stdout).to_not include('B1 test example')
1803
+ expect(actual.stdout).to_not include('B2 test example')
1804
+ expect(actual.stdout).to_not include('C1 test example')
1805
+ expect(actual.stdout).to_not include('C2 test example')
1806
+
1807
+ expect(actual.exit_code).to eq 1
1808
+ end
1809
+ end
1810
+
1811
+ context 'when the RSpec split by examples is enabled AND JSON formatter is used' do
1812
+ let(:json_file) { "#{SPEC_DIRECTORY}/rspec.json" }
1813
+
1814
+ before do
1815
+ ENV['KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES'] = 'true'
1816
+
1817
+ # remember to mock Queue API batches to include test examples (example: a_spec.rb[1:1])
1818
+ # for the following slow test files
1819
+ ENV['KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN'] = "#{SPEC_DIRECTORY}/a_spec.rb"
1820
+ end
1821
+ after do
1822
+ ENV.delete('KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES')
1823
+ ENV.delete('KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN')
1824
+ end
1825
+
1826
+ it 'produces a JSON report' do
1827
+ rspec_options = "--format documentation --format json --out ./#{json_file}"
1828
+
1829
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1830
+ describe 'A_describe' do
1831
+ it 'A1 test example' do
1832
+ expect(1).to eq 1
1833
+ end
1834
+ it 'A2 test example' do
1835
+ expect(1).to eq 1
1836
+ end
1837
+ end
1838
+ SPEC
1839
+
1840
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1841
+ describe 'B_describe' do
1842
+ it 'B1 test example' do
1843
+ expect(1).to eq 1
1844
+ end
1845
+ it 'B2 test example' do
1846
+ expect(1).to eq 1
1847
+ end
1848
+ end
1849
+ SPEC
1850
+
1851
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1852
+ describe 'C_describe' do
1853
+ it 'C1 test example' do
1854
+ expect(1).to eq 1
1855
+ end
1856
+ it 'C2 test example' do
1857
+ expect(1).to eq 1
1858
+ end
1859
+ end
1860
+ SPEC
1861
+
1862
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1863
+ [spec_a, spec_b, spec_c]
1864
+ ])
1865
+ mock_test_cases_for_slow_test_files([
1866
+ "#{spec_a.path}[1:1]",
1867
+ "#{spec_a.path}[1:2]",
1868
+ ])
1869
+ stub_spec_batches([
1870
+ ["#{spec_a.path}[1:1]", spec_b.path],
1871
+ ["#{spec_a.path}[1:2]", spec_c.path],
1872
+ ])
1873
+
1874
+ actual = subject
1875
+
1876
+ file_content = File.read(json_file)
1877
+ json = JSON.load(file_content)
1878
+ examples = json.fetch('examples')
1879
+
1880
+ example_ids = examples.map do
1881
+ _1.fetch('id')
1882
+ end
1883
+ expect(example_ids).to match_array([
1884
+ './spec_integration/a_spec.rb[1:1]',
1885
+ './spec_integration/b_spec.rb[1:1]',
1886
+ './spec_integration/b_spec.rb[1:2]',
1887
+ './spec_integration/a_spec.rb[1:2]',
1888
+ './spec_integration/c_spec.rb[1:1]',
1889
+ './spec_integration/c_spec.rb[1:2]'
1890
+ ])
1891
+
1892
+ example_full_descriptions = examples.map do
1893
+ _1.fetch('full_description')
1894
+ end
1895
+ expect(example_full_descriptions).to match_array([
1896
+ 'A_describe A1 test example',
1897
+ 'B_describe B1 test example',
1898
+ 'B_describe B2 test example',
1899
+ 'A_describe A2 test example',
1900
+ 'C_describe C1 test example',
1901
+ 'C_describe C2 test example'
1902
+ ])
1903
+
1904
+ expect(json.fetch('summary_line')).to eq '6 examples, 0 failures'
1905
+
1906
+ expect(actual.exit_code).to eq 0
1907
+ end
1908
+ end
1909
+
1910
+ context 'when the RSpec split by examples is enabled AND JUnit XML formatter is used' do
1911
+ let(:xml_file) { "#{SPEC_DIRECTORY}/rspec.xml" }
1912
+
1913
+ before do
1914
+ ENV['KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES'] = 'true'
1915
+
1916
+ # remember to mock Queue API batches to include test examples (example: a_spec.rb[1:1])
1917
+ # for the following slow test files
1918
+ ENV['KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN'] = "#{SPEC_DIRECTORY}/a_spec.rb"
1919
+ end
1920
+ after do
1921
+ ENV.delete('KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES')
1922
+ ENV.delete('KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN')
1923
+ end
1924
+
1925
+ it 'produces a JUnit XML report' do
1926
+ rspec_options = "--format documentation --format RspecJunitFormatter --out ./#{xml_file}"
1927
+
1928
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
1929
+ describe 'A_describe' do
1930
+ it 'A1 test example' do
1931
+ expect(1).to eq 1
1932
+ end
1933
+ it 'A2 test example' do
1934
+ expect(1).to eq 1
1935
+ end
1936
+ end
1937
+ SPEC
1938
+
1939
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
1940
+ describe 'B_describe' do
1941
+ it 'B1 test example' do
1942
+ expect(1).to eq 1
1943
+ end
1944
+ it 'B2 test example' do
1945
+ expect(1).to eq 1
1946
+ end
1947
+ end
1948
+ SPEC
1949
+
1950
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
1951
+ describe 'C_describe' do
1952
+ it 'C1 test example' do
1953
+ expect(1).to eq 1
1954
+ end
1955
+ it 'C2 test example' do
1956
+ expect(1).to eq 1
1957
+ end
1958
+ end
1959
+ SPEC
1960
+
1961
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
1962
+ [spec_a, spec_b, spec_c]
1963
+ ])
1964
+ mock_test_cases_for_slow_test_files([
1965
+ "#{spec_a.path}[1:1]",
1966
+ "#{spec_a.path}[1:2]",
1967
+ ])
1968
+ stub_spec_batches([
1969
+ ["#{spec_a.path}[1:1]", spec_b.path],
1970
+ ["#{spec_a.path}[1:2]", spec_c.path],
1971
+ ])
1972
+
1973
+ actual = subject
1974
+
1975
+ file_content = File.read(xml_file)
1976
+ doc = Nokogiri::XML(file_content)
1977
+
1978
+ files = doc.xpath('//testcase').map do |testcase|
1979
+ testcase['file']
1980
+ end
1981
+ expect(files).to eq([
1982
+ './spec_integration/a_spec.rb',
1983
+ './spec_integration/b_spec.rb',
1984
+ './spec_integration/b_spec.rb',
1985
+ './spec_integration/a_spec.rb',
1986
+ './spec_integration/c_spec.rb',
1987
+ './spec_integration/c_spec.rb',
1988
+ ])
1989
+
1990
+ examples = doc.xpath('//testcase').map do |testcase|
1991
+ testcase['name']
1992
+ end
1993
+ expect(examples).to eq([
1994
+ 'A_describe A1 test example',
1995
+ 'B_describe B1 test example',
1996
+ 'B_describe B2 test example',
1997
+ 'A_describe A2 test example',
1998
+ 'C_describe C1 test example',
1999
+ 'C_describe C2 test example',
2000
+ ])
2001
+
2002
+ expect(actual.exit_code).to eq 0
2003
+ end
2004
+ end
2005
+
2006
+ context 'when the RSpec split by examples is enabled AND simplecov is used' do
2007
+ let(:coverage_dir) { "#{KNAPSACK_PRO_TMP_DIR}/coverage" }
2008
+ let(:coverage_file) { "#{coverage_dir}/index.html" }
2009
+
2010
+ before do
2011
+ ENV['KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES'] = 'true'
2012
+
2013
+ # remember to mock Queue API batches to include test examples (example: a_spec.rb[1:1])
2014
+ # for the following slow test files
2015
+ ENV['KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN'] = "#{SPEC_DIRECTORY}/a_spec.rb"
2016
+ end
2017
+ after do
2018
+ ENV.delete('KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES')
2019
+ ENV.delete('KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN')
2020
+ end
2021
+
2022
+ it 'produces a code coverage report' do
2023
+ rspec_options = '--format documentation'
2024
+
2025
+ spec_helper = <<~SPEC
2026
+ require 'knapsack_pro'
2027
+ KnapsackPro::Adapters::RSpecAdapter.bind
2028
+
2029
+ require 'simplecov'
2030
+ SimpleCov.start do
2031
+ coverage_dir '#{coverage_dir}'
2032
+ end
2033
+ SPEC
2034
+
2035
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
2036
+ describe 'A_describe' do
2037
+ it 'A1 test example' do
2038
+ expect(1).to eq 1
2039
+ end
2040
+ it 'A2 test example' do
2041
+ expect(1).to eq 1
2042
+ end
2043
+ end
2044
+ SPEC
2045
+
2046
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
2047
+ describe 'B_describe' do
2048
+ it 'B1 test example' do
2049
+ expect(1).to eq 1
2050
+ end
2051
+ it 'B2 test example' do
2052
+ expect(1).to eq 1
2053
+ end
2054
+ end
2055
+ SPEC
2056
+
2057
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
2058
+ describe 'C_describe' do
2059
+ it 'C1 test example' do
2060
+ expect(1).to eq 1
2061
+ end
2062
+ it 'C2 test example' do
2063
+ expect(1).to eq 1
2064
+ end
2065
+ end
2066
+ SPEC
2067
+
2068
+ generate_specs(spec_helper, rspec_options, [
2069
+ [spec_a, spec_b, spec_c]
2070
+ ])
2071
+ mock_test_cases_for_slow_test_files([
2072
+ "#{spec_a.path}[1:1]",
2073
+ "#{spec_a.path}[1:2]",
2074
+ ])
2075
+ stub_spec_batches([
2076
+ ["#{spec_a.path}[1:1]", spec_b.path],
2077
+ ["#{spec_a.path}[1:2]", spec_c.path],
2078
+ ])
2079
+
2080
+ actual = subject
2081
+
2082
+ file_content = File.read(coverage_file)
2083
+
2084
+ expect(file_content).to include(spec_a.path)
2085
+ expect(file_content).to include(spec_b.path)
2086
+ expect(file_content).to include(spec_c.path)
2087
+
2088
+ expect(actual.exit_code).to eq 0
2089
+ end
2090
+ end
2091
+
2092
+ context 'when the example_status_persistence_file_path option is used and multiple batches of tests are fetched from the Queue API and some tests are pending and failing' do
2093
+ let(:examples_file_path) { "#{SPEC_DIRECTORY}/examples.txt" }
2094
+
2095
+ after do
2096
+ File.delete(examples_file_path) if File.exist?(examples_file_path)
2097
+ end
2098
+
2099
+ it 'runs tests AND creates the example status persistence file' do
2100
+ rspec_options = '--format d'
2101
+
2102
+ spec_helper = <<~SPEC
2103
+ require 'knapsack_pro'
2104
+ KnapsackPro::Adapters::RSpecAdapter.bind
2105
+
2106
+ RSpec.configure do |config|
2107
+ config.example_status_persistence_file_path = '#{examples_file_path}'
2108
+ end
2109
+ SPEC
2110
+
2111
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
2112
+ describe 'A_describe' do
2113
+ xit 'A1 test example' do
2114
+ expect(1).to eq 1
2115
+ end
2116
+ end
2117
+ SPEC
2118
+
2119
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
2120
+ describe 'B_describe' do
2121
+ it 'B1 test example' do
2122
+ expect(1).to eq 0
2123
+ end
2124
+ end
2125
+ SPEC
2126
+
2127
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
2128
+ describe 'C_describe' do
2129
+ it 'C1 test example' do
2130
+ expect(1).to eq 1
2131
+ end
2132
+ it 'C2 test example' do
2133
+ expect(1).to eq 0
2134
+ end
2135
+ end
2136
+ SPEC
2137
+
2138
+ generate_specs(spec_helper, rspec_options, [
2139
+ [spec_a, spec_b],
2140
+ [spec_c],
2141
+ ])
2142
+
2143
+ actual = subject
2144
+
2145
+ expect(actual.stdout).to include('4 examples, 2 failures, 1 pending')
2146
+
2147
+ expect(File.exist?(examples_file_path)).to be true
2148
+
2149
+ examples_file_content = File.read(examples_file_path)
2150
+
2151
+ expect(examples_file_content).to include './spec_integration/a_spec.rb[1:1] | pending'
2152
+ expect(examples_file_content).to include './spec_integration/b_spec.rb[1:1] | failed'
2153
+ expect(examples_file_content).to include './spec_integration/c_spec.rb[1:1] | passed'
2154
+ expect(examples_file_content).to include './spec_integration/c_spec.rb[1:2] | failed'
2155
+
2156
+ expect(actual.exit_code).to eq 1
2157
+ end
2158
+ end
2159
+
2160
+ context 'when the .rspec file has RSpec options' do
2161
+ let(:dot_rspec_file) { "#{SPEC_DIRECTORY}/.rspec" }
2162
+
2163
+ it 'ignores options from the .rspec file' do
2164
+ File.open(dot_rspec_file, 'w') { |file| file.write('--format documentation') }
2165
+
2166
+ rspec_options = ''
2167
+
2168
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
2169
+ describe 'A_describe' do
2170
+ it 'A1 test example' do
2171
+ expect(1).to eq 1
2172
+ end
2173
+ end
2174
+ SPEC
2175
+
2176
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
2177
+ [spec_a],
2178
+ ])
2179
+
2180
+ actual = subject
2181
+
2182
+ expect(actual.stdout).not_to include('A1 test example')
2183
+
2184
+ expect(actual.stdout).to include('1 example, 0 failures')
2185
+
2186
+ expect(actual.exit_code).to eq 0
2187
+ end
2188
+ end
2189
+
2190
+ context 'when --options is set' do
2191
+ let(:rspec_custom_options_file) { "#{SPEC_DIRECTORY}/.rspec_custom_options" }
2192
+
2193
+ it 'uses options from the custom rspec file' do
2194
+ rspec_custom_options = <<~FILE
2195
+ --require spec_helper
2196
+ --profile
2197
+ FILE
2198
+ File.open(rspec_custom_options_file, 'w') { |file| file.write(rspec_custom_options) }
2199
+
2200
+ rspec_options = "--options ./#{rspec_custom_options_file}"
2201
+
2202
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
2203
+ describe 'A_describe' do
2204
+ it 'A1 test example' do
2205
+ expect(1).to eq 1
2206
+ end
2207
+ end
2208
+ SPEC
2209
+
2210
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
2211
+ describe 'B_describe' do
2212
+ it 'B1 test example' do
2213
+ expect(1).to eq 1
2214
+ end
2215
+ end
2216
+ SPEC
2217
+
2218
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
2219
+ [spec_a],
2220
+ [spec_b],
2221
+ ])
2222
+
2223
+ actual = subject
2224
+
2225
+ expect(actual.stdout).to include('2 examples, 0 failures')
2226
+
2227
+ expect(actual.stdout).to include('Top 2 slowest example groups')
2228
+
2229
+ expect(actual.exit_code).to eq 0
2230
+ end
2231
+ end
2232
+ end