Commit 22cc18eb authored by Albert Salim's avatar Albert Salim

Add custom parallel rspec runner

A custom parallel rspec runner based on Knapsack runner
which takes in additional option for a file containing
list of test files.

When executing RSpec in CI, the list of tests allocated by Knapsack
will be compared with the test files listed in the file.

Only the test files allocated by Knapsack and listed in the file
would be executed in the CI node.
parent b19a6b8a
......@@ -56,7 +56,7 @@ function update_tests_mapping() {
}
function crystalball_rspec_data_exists() {
compgen -G "crystalball/rspec*.yml" > /dev/null;
compgen -G "crystalball/rspec*.yml" >/dev/null
}
function rspec_simple_job() {
......@@ -117,7 +117,13 @@ function rspec_paralellized_job() {
export MEMORY_TEST_PATH="tmp/memory_test/${report_name}_memory.csv"
knapsack rspec "-Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}"
local rspec_args="-Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}"
if [[ -n $RSPEC_MATCHING_TESTS_ENABLED ]]; then
tooling/bin/parallel_rspec --rspec_args "${rspec_args}" --filter tmp/matching_tests.txt
else
tooling/bin/parallel_rspec --rspec_args "${rspec_args}"
fi
date
}
......
# frozen_string_literal: true
require_relative '../../../../tooling/lib/tooling/parallel_rspec_runner'
RSpec.describe Tooling::ParallelRSpecRunner do # rubocop:disable RSpec/FilePath
describe '#run' do
let(:allocator) { instance_double(Knapsack::Allocator) }
let(:rspec_args) { '--seed 123' }
let(:filter_tests_file) { 'tests.txt' }
let(:node_tests) { %w[01_spec.rb 03_spec.rb 05_spec.rb] }
let(:filter_tests) { '01_spec.rb 02_spec.rb 03_spec.rb' }
let(:test_dir) { 'spec' }
before do
allow(Knapsack.logger).to receive(:info)
allow(allocator).to receive(:node_tests).and_return(node_tests)
allow(allocator).to receive(:test_dir).and_return(test_dir)
allow(File).to receive(:exist?).with(filter_tests_file).and_return(true)
allow(File).to receive(:read).and_call_original
allow(File).to receive(:read).with(filter_tests_file).and_return(filter_tests)
allow(subject).to receive(:exec)
end
subject { described_class.new(allocator: allocator, filter_tests_file: filter_tests_file, rspec_args: rspec_args) }
shared_examples 'runs node tests' do
it 'runs rspec with tests allocated for this node' do
expect_command(%w[bundle exec rspec --seed 123 --default-path spec -- 01_spec.rb 03_spec.rb 05_spec.rb])
subject.run
end
end
context 'given filter tests' do
it 'reads filter tests file for list of tests' do
expect(File).to receive(:read).with(filter_tests_file)
subject.run
end
it 'runs rspec filter tests that are allocated for this node' do
expect_command(%w[bundle exec rspec --seed 123 --default-path spec -- 01_spec.rb 03_spec.rb])
subject.run
end
end
context 'with empty filter tests file' do
let(:filter_tests) { '' }
it_behaves_like 'runs node tests'
end
context 'without filter_tests_file option' do
let(:filter_tests_file) { nil }
it_behaves_like 'runs node tests'
end
context 'if filter_tests_file does not exist' do
before do
allow(File).to receive(:exist?).with(filter_tests_file).and_return(false)
end
it_behaves_like 'runs node tests'
end
context 'without rspec args' do
let(:rspec_args) { nil }
it 'runs rspec with without extra arguments' do
expect_command(%w[bundle exec rspec --default-path spec -- 01_spec.rb 03_spec.rb])
subject.run
end
end
def expect_command(cmd)
expect(subject).to receive(:exec).with(*cmd)
end
end
end
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'optparse'
require_relative '../lib/tooling/parallel_rspec_runner'
options = {}
OptionParser.new do |opts|
opts.on("--rspec_args rspec_args", String, "Optional rspec arguments") do |value|
options[:rspec_args] = value
end
opts.on("--filter filter_tests_file", String, "Optional filename containing tests to be filtered") do |value|
options[:filter_tests_file] = value
end
end.parse!
Tooling::ParallelRSpecRunner.run(options)
# frozen_string_literal: true
require 'knapsack'
# A custom parallel rspec runner based on Knapsack runner
# which takes in additional option for a file containing
# list of test files.
#
# When executing RSpec in CI, the list of tests allocated by Knapsack
# will be compared with the test files listed in the file.
#
# Only the test files allocated by Knapsack and listed in the file
# would be executed in the CI node.
module Tooling
class ParallelRSpecRunner
def self.run(rspec_args: nil, filter_tests_file: nil)
new(rspec_args: rspec_args, filter_tests_file: filter_tests_file).run
end
def initialize(allocator: knapsack_allocator, filter_tests_file: nil, rspec_args: nil)
@allocator = allocator
@filter_tests_file = filter_tests_file
@rspec_args = rspec_args&.split(' ') || []
end
def run
Knapsack.logger.info
Knapsack.logger.info 'Knapsack node specs:'
Knapsack.logger.info node_tests
Knapsack.logger.info
Knapsack.logger.info 'Filter specs:'
Knapsack.logger.info filter_tests
Knapsack.logger.info
Knapsack.logger.info 'Running specs:'
Knapsack.logger.info tests_to_run
Knapsack.logger.info
exec(*rspec_command)
end
private
attr_reader :allocator, :filter_tests_file, :rspec_args
def rspec_command
%w[bundle exec rspec].tap do |cmd|
cmd.push(*rspec_args)
cmd.push('--default-path', allocator.test_dir)
cmd.push('--')
cmd.push(*tests_to_run)
end
end
def tests_to_run
return node_tests if filter_tests.empty?
node_tests & filter_tests
end
def node_tests
allocator.node_tests
end
def filter_tests
filter_tests_file ? tests_from_file(filter_tests_file) : []
end
def tests_from_file(filter_tests_file)
return [] unless File.exist?(filter_tests_file)
File.read(filter_tests_file).split(' ')
end
def knapsack_allocator
Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment