summaryrefslogtreecommitdiff
path: root/tooling/lib/tooling/parallel_rspec_runner.rb
blob: b1ddc91e831e3048e3a96b87759149ea41771e55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 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.
#
# Reference:
# https://github.com/ArturT/knapsack/blob/v1.20.0/lib/knapsack/runners/rspec_runner.rb
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

      if tests_to_run.empty?
        Knapsack.logger.info 'No tests to run on this node, exiting.'
        return
      end

      Knapsack.logger.info "Running command: #{rspec_command.join(' ')}"

      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
      if filter_tests.empty?
        Knapsack.logger.info 'Running all node tests without filter'
        return node_tests
      end

      @tests_to_run ||= node_tests & filter_tests
    end

    def node_tests
      allocator.node_tests
    end

    def filter_tests
      @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