knapsack_pro 6.0.4 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
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