-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
spec_runner.rake
119 lines (98 loc) · 3.35 KB
/
spec_runner.rake
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# frozen_string_literal: true
require 'rspec/core'
require 'test_queue'
require 'test_queue/runner/rspec'
module RuboCop
# Helper for running specs with a temporary external encoding.
# This is a bit risky, since strings defined before the block may have a
# different encoding than strings defined inside the block.
# The specs will be run in parallel if the system implements `fork`.
# If ENV['COVERAGE'] is truthy, code coverage will be measured.
class SpecRunner
attr_reader :rspec_args
def initialize(rspec_args = %w[spec], external_encoding: 'UTF-8', internal_encoding: nil)
@rspec_args = rspec_args
@previous_external_encoding = Encoding.default_external
@previous_internal_encoding = Encoding.default_internal
@temporary_external_encoding = external_encoding
@temporary_internal_encoding = internal_encoding
end
def run_specs
n_failures = with_encoding do
if Process.respond_to?(:fork)
parallel_runner_klass.new(rspec_args).execute
else
::RSpec::Core::Runner.run(rspec_args)
end
end
exit!(n_failures) unless n_failures.zero?
end
private
def with_encoding
Encoding.default_external = @temporary_external_encoding
Encoding.default_internal = @temporary_internal_encoding
yield
ensure
Encoding.default_external = @previous_external_encoding
Encoding.default_internal = @previous_internal_encoding
end
def parallel_runner_klass
if ENV['COVERAGE']
ParallelCoverageRunner
else
ParallelRunner
end
end
# A parallel spec runner implementation, heavily inspired by
# `TestQueue::Runner::RSpec`, but modified so that it takes an argument
# (an array of paths of specs to run) instead of relying on ARGV.
class ParallelRunner < ::TestQueue::Runner
def initialize(rspec_args)
super(Framework.new(rspec_args))
@exit_when_done = false
end
def run_worker(iterator)
rspec = ::RSpec::Core::QueueRunner.new
rspec.run_each(iterator).to_i
end
def summarize_worker(worker)
worker.summary = worker.lines.grep(/\A\d+ examples?, /).first
worker.failure_output = worker.output[
/^Failures:\n\n(.*)\n^Finished/m, 1]
end
end
# A custom runner for measuring code coverage in parallel.
class ParallelCoverageRunner < ParallelRunner
def after_fork(num)
SimpleCov.command_name "rspec-#{num}"
end
def cleanup_worker
SimpleCov.result
end
def summarize
SimpleCov.at_exit.call
end
end
# A TestQueue framework that is explicitly given RSpec arguments instead of
# implicitly reading ARGV.
class Framework < ::TestQueue::TestFramework::RSpec
def initialize(rspec_args)
@rspec_args = rspec_args
end
def all_suite_files
options = ::RSpec::Core::ConfigurationOptions.new(@rspec_args)
options.parse_options if options.respond_to?(:parse_options)
options.configure(::RSpec.configuration)
::RSpec.configuration.files_to_run.uniq
end
end
end
end
desc 'Run RSpec code examples'
task :spec do
RuboCop::SpecRunner.new.run_specs
end
desc 'Run RSpec code examples with ASCII encoding'
task :ascii_spec do
RuboCop::SpecRunner.new(external_encoding: 'ASCII').run_specs
end