Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
# Changelog

### 7.3.0

* [Queue Mode][RSpec] Pass each batch of tests to the queue hooks: `KnapsackPro::Hooks::Queue.before_subset_queue` and `KnapsackPro::Hooks::Queue.after_subset_queue`

The `KnapsackPro::Hooks::Queue.before_subset_queue` and `KnapsackPro::Hooks::Queue.after_subset_queue` hooks get a 3rd variable - the `queue`.

The `queue` variable stores an enumerable collection with each batch of tests fetched from the Queue API. The batch has:

* a list of test file paths (`KnapsackPro::Batch#test_file_paths` returns an array like `['a_spec.rb', 'b_spec.rb']`)
* a status of the given set of tests in the batch (`KnapsackPro::Batch#status` returns `:not_executed`, `:passed` or `:failed`)

Example usage:

```ruby
# spec_helper.rb

KnapsackPro::Hooks::Queue.before_subset_queue do |queue_id, subset_queue_id, queue|
print "Tests from all batches fetched from the Queue API so far: "
puts queue.map(&:test_file_paths).inspect

queue.each(&:test_file_paths) # you can use each as well

print "Current batch tests: "
puts queue.current_batch.test_file_paths.inspect

print "Current batch status: "
puts queue.current_batch.status # returns :not_executed in the `before_subset_queue` hook
end

KnapsackPro::Hooks::Queue.after_subset_queue do |queue_id, subset_queue_id, queue|
print "Tests from all batches fetched from the Queue API so far: "
puts queue.map(&:test_file_paths).inspect

print "Current batch tests: "
puts queue.current_batch.test_file_paths.inspect

print "Current batch status: "
puts queue.current_batch.status # returns :passed or :failed in the `after_subset_queue` hook
end
```

https://github.com/KnapsackPro/knapsack_pro-ruby/pull/253

https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v7.2.0...v7.3.0

### 7.2.0

* Always use the original `Net::HTTP` client, even when WebMock replaces it with its own
Expand Down
2 changes: 2 additions & 0 deletions lib/knapsack_pro.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
require_relative 'knapsack_pro/base_allocator_builder'
require_relative 'knapsack_pro/allocator_builder'
require_relative 'knapsack_pro/queue_allocator_builder'
require_relative 'knapsack_pro/batch'
require_relative 'knapsack_pro/queue'
require_relative 'knapsack_pro/runners/base_runner'
require_relative 'knapsack_pro/runners/rspec_runner'
require_relative 'knapsack_pro/runners/cucumber_runner'
Expand Down
20 changes: 20 additions & 0 deletions lib/knapsack_pro/batch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module KnapsackPro
class Batch
attr_reader :test_file_paths, :status

def initialize(test_file_paths)
@test_file_paths = test_file_paths
@status = :not_executed
end

def _passed
@status = :passed
end

def _failed
@status = :failed
end
end
end
9 changes: 7 additions & 2 deletions lib/knapsack_pro/extensions/rspec_extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,20 @@ def knapsack__run_specs(queue_runner)

configuration.reporter.report(_expected_example_count = 0) do |reporter|
configuration.with_suite_hooks do
queue_runner.with_batch do |test_file_paths|
queue_runner.with_batch do |test_file_paths, queue|
knapsack__load_spec_files_batch(test_file_paths)

examples_passed = ordering_strategy.order(world.example_groups).map do |example_group|
queue_runner.handle_signal!
example_group.run(reporter)
end.all?

node_examples_passed = false unless examples_passed
if examples_passed
queue.mark_batch_passed
else
queue.mark_batch_failed
node_examples_passed = false
end

knapsack__persist_example_statuses

Expand Down
11 changes: 7 additions & 4 deletions lib/knapsack_pro/hooks/queue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,25 @@ def call_before_queue
end
end

def call_before_subset_queue
# `queue` is always present for RSpec
def call_before_subset_queue(queue = nil)
Comment thread
ArturT marked this conversation as resolved.
return unless before_subset_queue_store
before_subset_queue_store.each do |block|
block.call(
KnapsackPro::Config::Env.queue_id,
KnapsackPro::Config::Env.subset_queue_id
KnapsackPro::Config::Env.subset_queue_id,
queue
)
end
end

def call_after_subset_queue
def call_after_subset_queue(queue = nil)
return unless after_subset_queue_store
after_subset_queue_store.each do |block|
block.call(
KnapsackPro::Config::Env.queue_id,
KnapsackPro::Config::Env.subset_queue_id
KnapsackPro::Config::Env.subset_queue_id,
queue
)
end
end
Expand Down
40 changes: 40 additions & 0 deletions lib/knapsack_pro/queue.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module KnapsackPro
class Queue
include Enumerable

def initialize
@batches = []
end

def each(&block)
@batches.each(&block)
end

def add_batch_for(test_file_paths)
return if test_file_paths.empty?
@batches << KnapsackPro::Batch.new(test_file_paths)
end

def mark_batch_passed
current_batch._passed
end

def mark_batch_failed
current_batch._failed
end

def current_batch
@batches.last
end

def size
@batches.size
end

def [](index)
@batches[index]
end
end
end
9 changes: 6 additions & 3 deletions lib/knapsack_pro/runners/queue/rspec_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def initialize(adapter_class, rspec_pure, args, stream_error, stream_out)
@stream_out = stream_out
@node_test_file_paths = []
@rspec_runner = nil # RSpec::Core::Runner is lazy initialized
@queue = KnapsackPro::Queue.new
end

# Based on:
Expand Down Expand Up @@ -82,11 +83,13 @@ def with_batch
subset_queue_id = KnapsackPro::Config::EnvGenerator.set_subset_queue_id
ENV['KNAPSACK_PRO_SUBSET_QUEUE_ID'] = subset_queue_id

KnapsackPro::Hooks::Queue.call_before_subset_queue
@queue.add_batch_for(test_file_paths)

yield test_file_paths
KnapsackPro::Hooks::Queue.call_before_subset_queue(@queue)

KnapsackPro::Hooks::Queue.call_after_subset_queue
yield test_file_paths, @queue

KnapsackPro::Hooks::Queue.call_after_subset_queue(@queue)

if @rspec_runner.knapsack__wants_to_quit?
KnapsackPro.logger.warn('RSpec wants to quit.')
Expand Down
133 changes: 133 additions & 0 deletions spec/integration/runners/queue/rspec_runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,139 @@ def when_first_matching_example_defined(type:)

expect(actual.exit_code).to eq 0
end

it 'gives access to batch of tests in queue hooks' do
rspec_options = ''

spec_helper = <<~SPEC
require 'knapsack_pro'
KnapsackPro::Adapters::RSpecAdapter.bind

KnapsackPro::Hooks::Queue.before_subset_queue do |queue_id, subset_queue_id, queue|
print "Tests in batches in before_subset_queue: "
puts queue.map(&:test_file_paths).inspect

print "Batches' statuses in before_subset_queue: "
puts queue.map(&:status).inspect
end

KnapsackPro::Hooks::Queue.after_subset_queue do |queue_id, subset_queue_id, queue|
print "Tests in batches in after_subset_queue: "
puts queue.map(&:test_file_paths).inspect
print "Batches' statuses in after_subset_queue: "
puts queue.map(&:status).inspect

# call public API methods that must be backward compatible
print "Current batch tests: "
puts queue.current_batch.test_file_paths.inspect
print "Current batch status: "
puts queue.current_batch.status
end
SPEC

spec_a = Spec.new('a_spec.rb', <<~SPEC)
describe 'A_describe' do
it 'A1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_b = Spec.new('b_spec.rb', <<~SPEC)
describe 'B_describe' do
it 'B1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_c = Spec.new('c_spec.rb', <<~SPEC)
describe 'C_describe' do
it 'C1 test example' do
expect(1).to eq 1
end
end
SPEC

failing_spec_d = Spec.new('d_spec.rb', <<~SPEC)
describe 'D_describe' do
it 'D1 test example' do
expect(1).to eq 0
end
end
SPEC

spec_e = Spec.new('e_spec.rb', <<~SPEC)
describe 'E_describe' do
it 'E1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_f = Spec.new('f_spec.rb', <<~SPEC)
describe 'F_describe' do
it 'F1 test example' do
expect(1).to eq 1
end
end
SPEC

failing_spec_g = Spec.new('g_spec.rb', <<~SPEC)
describe 'G_describe' do
it 'G1 test example' do
expect(1).to eq 0
end
end
SPEC

spec_h = Spec.new('h_spec.rb', <<~SPEC)
describe 'h_describe' do
it 'H1 test example' do
expect(1).to eq 1
end
end
SPEC

generate_specs(spec_helper, rspec_options, [
[spec_a, spec_b],
[spec_c, failing_spec_d],
[spec_e, spec_f],
[failing_spec_g, spec_h],
])

actual = subject

expect(actual.stdout).to include('Tests in batches in before_subset_queue: [["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"]]')
expect(actual.stdout).to include('Tests in batches in before_subset_queue: [["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"], ["spec_integration/c_spec.rb", "spec_integration/d_spec.rb"]]')
expect(actual.stdout).to include('Tests in batches in before_subset_queue: [["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"], ["spec_integration/c_spec.rb", "spec_integration/d_spec.rb"], ["spec_integration/e_spec.rb", "spec_integration/f_spec.rb"]]')
expect(actual.stdout).to include('Tests in batches in before_subset_queue: [["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"], ["spec_integration/c_spec.rb", "spec_integration/d_spec.rb"], ["spec_integration/e_spec.rb", "spec_integration/f_spec.rb"], ["spec_integration/g_spec.rb", "spec_integration/h_spec.rb"]]')

expect(actual.stdout).to include('Tests in batches in after_subset_queue: [["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"]]')
expect(actual.stdout).to include('Tests in batches in after_subset_queue: [["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"], ["spec_integration/c_spec.rb", "spec_integration/d_spec.rb"]]')
expect(actual.stdout).to include('Tests in batches in after_subset_queue: [["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"], ["spec_integration/c_spec.rb", "spec_integration/d_spec.rb"], ["spec_integration/e_spec.rb", "spec_integration/f_spec.rb"]]')
expect(actual.stdout).to include('Tests in batches in after_subset_queue: [["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"], ["spec_integration/c_spec.rb", "spec_integration/d_spec.rb"], ["spec_integration/e_spec.rb", "spec_integration/f_spec.rb"], ["spec_integration/g_spec.rb", "spec_integration/h_spec.rb"]]')


expect(actual.stdout).to include("Batches' statuses in before_subset_queue: [:not_executed]")
expect(actual.stdout).to include("Batches' statuses in before_subset_queue: [:passed, :not_executed]")
expect(actual.stdout).to include("Batches' statuses in before_subset_queue: [:passed, :failed, :not_executed]")
expect(actual.stdout).to include("Batches' statuses in before_subset_queue: [:passed, :failed, :passed, :not_executed]")

expect(actual.stdout).to include("Batches' statuses in after_subset_queue: [:passed]")
expect(actual.stdout).to include("Batches' statuses in after_subset_queue: [:passed, :failed]")
expect(actual.stdout).to include("Batches' statuses in after_subset_queue: [:passed, :failed, :passed]")
expect(actual.stdout).to include("Batches' statuses in after_subset_queue: [:passed, :failed, :passed, :failed]")

expect(actual.stdout).to include('Current batch tests: ["spec_integration/a_spec.rb", "spec_integration/b_spec.rb"]')
expect(actual.stdout).to include('Current batch tests: ["spec_integration/c_spec.rb", "spec_integration/d_spec.rb"]')
expect(actual.stdout).to include('Current batch tests: ["spec_integration/e_spec.rb", "spec_integration/f_spec.rb"]')
expect(actual.stdout).to include('Current batch tests: ["spec_integration/g_spec.rb", "spec_integration/h_spec.rb"]')
expect(actual.stdout).to include('Current batch status: passed').twice
expect(actual.stdout).to include('Current batch status: failed').twice

expect(actual.exit_code).to eq 1
end
end

context 'when the RSpec seed is used' do
Expand Down
Loading