knapsack_pro 6.0.3 → 7.0.0

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +375 -28
  3. data/.github/pull_request_template.md +22 -0
  4. data/.gitignore +4 -0
  5. data/CHANGELOG.md +95 -0
  6. data/Gemfile +9 -0
  7. data/README.md +0 -7
  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 +8 -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 +10 -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. data/spec/spec_helper.rb +0 -1
  39. metadata +17 -12
  40. data/lib/knapsack_pro/formatters/rspec_queue_profile_formatter_extension.rb +0 -58
  41. data/lib/knapsack_pro/formatters/rspec_queue_summary_formatter.rb +0 -145
  42. 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