knapsack_pro 6.0.4 → 7.0.1

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