paraspec 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +23 -0
- data/README.md +97 -0
- data/bin/paraspec +53 -0
- data/lib/paraspec.rb +21 -0
- data/lib/paraspec/drb_helpers.rb +65 -0
- data/lib/paraspec/http_client.rb +43 -0
- data/lib/paraspec/http_server.rb +24 -0
- data/lib/paraspec/ipc.rb +11 -0
- data/lib/paraspec/logger.rb +44 -0
- data/lib/paraspec/master.rb +219 -0
- data/lib/paraspec/master_runner.rb +7 -0
- data/lib/paraspec/msgpack_client.rb +53 -0
- data/lib/paraspec/msgpack_helpers.rb +46 -0
- data/lib/paraspec/msgpack_server.rb +58 -0
- data/lib/paraspec/process_helpers.rb +22 -0
- data/lib/paraspec/rspec_facade.rb +38 -0
- data/lib/paraspec/rspec_patches.rb +23 -0
- data/lib/paraspec/supervisor.rb +127 -0
- data/lib/paraspec/version.rb +3 -0
- data/lib/paraspec/worker.rb +70 -0
- data/lib/paraspec/worker_formatter.rb +82 -0
- data/lib/paraspec/worker_runner.rb +67 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 709786de26ffdac4d238bb0a1c531a654b7183386b0f85f0c523e74958f66f33
|
4
|
+
data.tar.gz: aa00e1665689abead274c8259a1ab55e1fc049b9e16029723e8c19269248d884
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 70f1e59627cc4d63ae6653b0f945831d9d7abac73cc998c76b1f8c36100e5b1a1f4578731c091904086a159be9ff4d3a16f24702f7847eb46cffac2c39947d4b
|
7
|
+
data.tar.gz: 06d5f5746820455ebe7fc0d440cc8f335a8c8ca6177a5d1a36afc0691d0854a9b2df31eb27b87726470be35139e7fdece0d2315e3bbbec07966ba4e7a0ae7801
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Copyright (c) 2018 Oleg Pudeyev
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
20
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
21
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
22
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
23
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Paraspec
|
2
|
+
|
3
|
+
Paraspec is a parallel RSpec test runner.
|
4
|
+
|
5
|
+
It is built with a producer/consumer architecture. A master process loads
|
6
|
+
the entire test suite and sets up a queue to feed the tests to the workers.
|
7
|
+
Each worker requests a test from the master, runs it, reports the results
|
8
|
+
back to the master and requests the next test until there are no more left.
|
9
|
+
|
10
|
+
This producer/consumer architecture enables a number of features:
|
11
|
+
|
12
|
+
1. The worker load is naturally balanced. If a worker happens to come across
|
13
|
+
a slow test, the other workers keep chugging away at faster tests.
|
14
|
+
2. Tests defined in a single file can be executed by multiple workers,
|
15
|
+
since paraspec operates on a test by test basis and not on a file by file basis.
|
16
|
+
3. Standard output and error streams can be[*] captured and grouped on a
|
17
|
+
test by test basis, avoiding interleaving output of different tests together.
|
18
|
+
This output capture can be performed for output generated by C extensions
|
19
|
+
as well as plain Ruby code.
|
20
|
+
4. Test results are seamlessly integrated by the master, such that
|
21
|
+
a parallel run produces a single progress bar with Fuubar across all workers.
|
22
|
+
|
23
|
+
[*] This feature is not yet implemented.
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
For a test suite with no external dependencies, using paraspec is
|
28
|
+
trivially easy. Just run:
|
29
|
+
|
30
|
+
paraspec
|
31
|
+
|
32
|
+
To specify concurrency manually:
|
33
|
+
|
34
|
+
paraspec -c 4
|
35
|
+
|
36
|
+
To pass options to rspec, for example to filter examples to run:
|
37
|
+
|
38
|
+
paraspec -- -e 'My test'
|
39
|
+
paraspec -- spec/my_spec.rb
|
40
|
+
|
41
|
+
For a test suite with external dependencies, paraspec sets the
|
42
|
+
`TEST_ENV_NUMBER` environment variable, like
|
43
|
+
[parallel_tests](https://github.com/grosser/parallel_tests) does.
|
44
|
+
The test suite can then configure itself differently in each worker.
|
45
|
+
|
46
|
+
By default the master process doesn't have `TEST_ENV_NUMBER` set.
|
47
|
+
To have that set to `1` use `--master-is-1` option to paraspec:
|
48
|
+
|
49
|
+
paraspec --master-is-1
|
50
|
+
|
51
|
+
## Advanced Usage
|
52
|
+
|
53
|
+
### Formatters
|
54
|
+
|
55
|
+
Paraspec works with any RSpec formatter, and supports multiple formatters
|
56
|
+
just like RSpec does. If your test suite is big enough for parallel execution
|
57
|
+
to make a difference, chances are the default progress and documentation
|
58
|
+
formatters aren't too useful for dealing with its output.
|
59
|
+
|
60
|
+
I recommend [Fuubar](https://github.com/thekompanee/fuubar) and
|
61
|
+
[RSpec JUnit Formatter](https://github.com/sj26/rspec_junit_formatter)
|
62
|
+
configured at the same time. Fuubar produces a very nice looking progress bar
|
63
|
+
plus it prints failures and exceptions to the terminal as soon as they
|
64
|
+
occur. JUnit output, passed through a JUnit XML to HTML converter like
|
65
|
+
[junit2html](https://gitlab.com/inorton/junit2html), is much handier
|
66
|
+
than going through terminal output when a run produces 100 or 1000
|
67
|
+
failing tests.
|
68
|
+
|
69
|
+
### Debugging
|
70
|
+
|
71
|
+
Paraspec offers several debugging aids. The first one is the terminal option:
|
72
|
+
|
73
|
+
paraspec -T
|
74
|
+
|
75
|
+
This option makes paraspec stay attached to the terminal it was
|
76
|
+
launched in, making it possible to insert e.g. `byebug` calls in supervisor,
|
77
|
+
master or worker code as well as anywhere in the test suite being executed
|
78
|
+
and have byebug work. Setting this option also removes internal timeouts
|
79
|
+
on interprocess waits and sets concurrency to 1, however concurrency
|
80
|
+
can be reset with a subsequent `-c` option:
|
81
|
+
|
82
|
+
paraspec -T -c 2
|
83
|
+
|
84
|
+
Paraspec can produce copious debugging output in several facilities.
|
85
|
+
The debugging output is turned on with `-d`/`--debug` option:
|
86
|
+
|
87
|
+
paraspec -d state # supervisor, master, worker state transitions
|
88
|
+
paraspec -d ipc # IPC requests and responses
|
89
|
+
paraspec -d perf # timing & performance information
|
90
|
+
|
91
|
+
## Bugs & Patches
|
92
|
+
|
93
|
+
Please report via issues and pull requests.
|
94
|
+
|
95
|
+
## License
|
96
|
+
|
97
|
+
MIT
|
data/bin/paraspec
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
4
|
+
|
5
|
+
require 'paraspec'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = 'Usage: paraspec [options] [-- [rspec-options]...]'
|
11
|
+
|
12
|
+
opts.on('-c', '--concurrency=NUM', 'Number of concurrent workers to use') do |v|
|
13
|
+
if v.to_i == 0
|
14
|
+
raise "Invalid concurrency value: #{v}"
|
15
|
+
end
|
16
|
+
options[:concurrency] = v.to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('-d', '--debug=SUBSYSTEM', 'Output debugging information for SUBSYSTEM') do |v|
|
20
|
+
options[:"debug_#{v}"] = true
|
21
|
+
options[:debug] = true
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on('-T', '--terminal', 'Retain controlling terminal (debug only)') do |v|
|
25
|
+
options[:terminal] = v
|
26
|
+
options[:concurrency] = 1
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('--master-is-1', 'Set TEST_ENV_NUMBER=1 in master process') do |v|
|
30
|
+
options[:master_is_1] = v
|
31
|
+
end
|
32
|
+
end.parse!
|
33
|
+
|
34
|
+
if options[:debug]
|
35
|
+
Paraspec.logger.level = Logger::DEBUG
|
36
|
+
end
|
37
|
+
|
38
|
+
=begin
|
39
|
+
files = if ARGV.length > 0
|
40
|
+
ARGV
|
41
|
+
else
|
42
|
+
['spec']
|
43
|
+
end
|
44
|
+
RSpec.configuration.files_or_directories_to_run = files
|
45
|
+
=end
|
46
|
+
|
47
|
+
%w(ipc state perf).each do |subsystem|
|
48
|
+
if options.delete(:"debug_#{subsystem}")
|
49
|
+
Paraspec.logger.send("log_#{subsystem}=", true)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
supervisor = Paraspec::Supervisor.new(options)
|
53
|
+
supervisor.run
|
data/lib/paraspec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'paraspec/rspec_patches'
|
2
|
+
require 'paraspec/version'
|
3
|
+
require 'paraspec/logger'
|
4
|
+
require 'paraspec/ipc'
|
5
|
+
require 'paraspec/drb_helpers'
|
6
|
+
require 'paraspec/process_helpers'
|
7
|
+
require 'paraspec/supervisor'
|
8
|
+
require 'paraspec/master'
|
9
|
+
require 'paraspec/worker'
|
10
|
+
require 'paraspec/master_runner'
|
11
|
+
require 'paraspec/worker_runner'
|
12
|
+
require 'paraspec/worker_formatter'
|
13
|
+
require 'paraspec/rspec_facade'
|
14
|
+
|
15
|
+
module Paraspec
|
16
|
+
autoload :HttpClient, 'paraspec/http_client'
|
17
|
+
autoload :HttpServer, 'paraspec/http_server'
|
18
|
+
autoload :MsgpackClient, 'paraspec/msgpack_client'
|
19
|
+
autoload :MsgpackServer, 'paraspec/msgpack_server'
|
20
|
+
autoload :MsgpackHelpers, 'paraspec/msgpack_helpers'
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Paraspec
|
4
|
+
module DrbHelpers
|
5
|
+
WAIT_TIME = 500
|
6
|
+
|
7
|
+
=begin
|
8
|
+
class TimeoutWrapper < BasicObject
|
9
|
+
def initialize(target, timeout)
|
10
|
+
@target, @timeout = target, timeout
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(m, *args)
|
14
|
+
::Timeout.timeout(@timeout) do
|
15
|
+
@target.send(m, *args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_to?(m, *args)
|
20
|
+
super(m, *args) || @target.respond_to?(m, *args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Connects to a DRb service and waits for the connection to start working.
|
25
|
+
#
|
26
|
+
# Interestingly, even when the remote end is up and running
|
27
|
+
# talking to it may fail the first time (or few times?)?
|
28
|
+
# Supervisor is able to invoke methods on master and subsequently
|
29
|
+
# when a worker connects to the master the DRb calls from worker fail.
|
30
|
+
# No idea why this is.
|
31
|
+
# Work around this by pinging and retrying each DRb connection
|
32
|
+
# prior to using it for real work.
|
33
|
+
#
|
34
|
+
# It appears that any DRb operation can also hang while producing
|
35
|
+
# no exceptions or output of any sort.
|
36
|
+
private def drb_connect(uri, timeout: true)
|
37
|
+
start_time = Time.now
|
38
|
+
Paraspec.logger.debug("#{ident} Connecting to DRb")
|
39
|
+
remote = TimeoutWrapper.new(DRbObject.new_with_uri(uri), 2)
|
40
|
+
Paraspec.logger.debug("#{ident} Waiting for DRb")
|
41
|
+
begin
|
42
|
+
# Assumes remote has a ping method
|
43
|
+
remote.ping
|
44
|
+
rescue DRb::DRbConnError, TypeError
|
45
|
+
raise if timeout && Time.now - start_time > WAIT_TIME
|
46
|
+
sleep 0.5
|
47
|
+
Paraspec.logger.debug("#{ident} Retrying DRb ping")
|
48
|
+
retry
|
49
|
+
rescue Timeout::Error
|
50
|
+
raise if timeout && Time.now - start_time > WAIT_TIME
|
51
|
+
Paraspec.logger.debug("#{ident} Reconnecting to DRb")
|
52
|
+
remote = TimeoutWrapper.new(DRbObject.new_with_uri(uri), 2)
|
53
|
+
retry
|
54
|
+
end
|
55
|
+
remote
|
56
|
+
end
|
57
|
+
=end
|
58
|
+
|
59
|
+
def master_client
|
60
|
+
# TODO pass terminal option
|
61
|
+
@master_client ||= MsgpackClient.new
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Paraspec
|
5
|
+
class HttpClient
|
6
|
+
def initialize(options={})
|
7
|
+
@terminal = options[:terminal]
|
8
|
+
|
9
|
+
@client = Faraday.new(url: "http://localhost:#{Paraspec::MASTER_APP_PORT}") do |client|
|
10
|
+
client.adapter :net_http
|
11
|
+
if @terminal
|
12
|
+
client.options.timeout = 100000
|
13
|
+
else
|
14
|
+
client.options.timeout = DrbHelpers::WAIT_TIME
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def request(action, payload=nil)
|
20
|
+
url = '/' + action
|
21
|
+
start_time = Time.now
|
22
|
+
begin
|
23
|
+
resp = @client.post(url) do |req|
|
24
|
+
if payload
|
25
|
+
req.headers['content-type'] = 'application/json'
|
26
|
+
req.body = payload.to_json
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if resp.status != 200
|
30
|
+
raise "Request failed: #{url} (#{resp.status})"
|
31
|
+
end
|
32
|
+
JSON.parse(resp.body)
|
33
|
+
rescue Faraday::ConnectionFailed
|
34
|
+
if !@terminal && Time.now - start_time > DrbHelpers::WAIT_TIME
|
35
|
+
raise
|
36
|
+
else
|
37
|
+
sleep 0.1
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
|
3
|
+
module Paraspec
|
4
|
+
class HttpServer < Sinatra::Base
|
5
|
+
post '/:action' do
|
6
|
+
action = params[:action]
|
7
|
+
body = request.body.read
|
8
|
+
if body.empty?
|
9
|
+
args = []
|
10
|
+
else
|
11
|
+
payload = JSON.parse(body)
|
12
|
+
payload = IpcHash.new.merge(payload)
|
13
|
+
args = [payload]
|
14
|
+
end
|
15
|
+
|
16
|
+
master = self.class.settings.master
|
17
|
+
action = action.gsub('-', '_')
|
18
|
+
result = master.send(action, *args)
|
19
|
+
|
20
|
+
content_type 'application/json'
|
21
|
+
(result || {}).to_json
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/paraspec/ipc.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Paraspec
|
4
|
+
class LoggerWrapper
|
5
|
+
def initialize(logger)
|
6
|
+
@logger = logger
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(m, *args)
|
10
|
+
@logger.send(m, *args)
|
11
|
+
end
|
12
|
+
|
13
|
+
%w(ipc state perf).each do |subsystem|
|
14
|
+
define_method "log_#{subsystem}=" do |v|
|
15
|
+
@subsystems ||= {}
|
16
|
+
@subsystems[subsystem] = v
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method "log_#{subsystem}?" do
|
20
|
+
@subsystems && @subsystems[subsystem] or false
|
21
|
+
end
|
22
|
+
|
23
|
+
define_method "debug_#{subsystem}" do |*args|
|
24
|
+
if send("log_#{subsystem}?")
|
25
|
+
msg = "#{ident || '[?]'} [#{subsystem}] #{args.shift}"
|
26
|
+
debug(msg, *args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :ident
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
attr_reader :logger
|
36
|
+
|
37
|
+
def logger=(logger)
|
38
|
+
@logger = LoggerWrapper.new(logger)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
self.logger = Logger.new(STDERR)
|
43
|
+
self.logger.level = Logger::WARN
|
44
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
module Paraspec
|
2
|
+
# The master process has three responsibilities:
|
3
|
+
# 1. Load all tests and abort the run if there are errors outside of
|
4
|
+
# examples.
|
5
|
+
# 2. Maintain the queue of tests to feed the workers. The master
|
6
|
+
# process also synchronizes access to this queue.
|
7
|
+
# 3. Aggregate test reports from the workers and present them to
|
8
|
+
# the outside world in a coherent fashion. The latter means
|
9
|
+
# that numbers presented are for the entire suite, not for parts
|
10
|
+
# of it as executed by any single worker, and that output from a
|
11
|
+
# single test execution is not broken up by output from other test
|
12
|
+
# executions.
|
13
|
+
class Master
|
14
|
+
def initialize(options={})
|
15
|
+
@supervisor_pipe = options[:supervisor_pipe]
|
16
|
+
#RSpec.configuration.formatter = 'progress'
|
17
|
+
if RSpec.world.example_groups.count > 0
|
18
|
+
raise 'Example groups loaded too early/spilled across processes'
|
19
|
+
end
|
20
|
+
|
21
|
+
rspec_options = RSpec::Core::ConfigurationOptions.new(ARGV)
|
22
|
+
@non_example_exception_count = 0
|
23
|
+
begin
|
24
|
+
# This can fail if for example a nonexistent formatter is referenced
|
25
|
+
rspec_options.configure(RSpec.configuration)
|
26
|
+
rescue Exception => e
|
27
|
+
puts "#{e.class}: #{e}"
|
28
|
+
puts e.backtrace.join("\n")
|
29
|
+
# TODO and report this situation as a configuration problem
|
30
|
+
# and not a test suite problem
|
31
|
+
@non_example_exception_count = 1
|
32
|
+
end
|
33
|
+
|
34
|
+
=begin
|
35
|
+
if RSpec.configuration.files_to_run.empty?
|
36
|
+
RSpec.configuration.send(:remove_instance_variable, '@files_to_run')
|
37
|
+
RSpec.configuration.files_or_directories_to_run = RSpec.configuration.default_path
|
38
|
+
RSpec.configuration.files_to_run
|
39
|
+
p ['aa1',RSpec.configuration.files_to_run]
|
40
|
+
rspec_options.configure(RSpec.configuration)
|
41
|
+
RSpec.configuration.load_spec_files
|
42
|
+
end
|
43
|
+
=end
|
44
|
+
|
45
|
+
# It seems that load_spec_files sometimes rescues exceptions outside of
|
46
|
+
# examples and sometimes does not, handle it both ways
|
47
|
+
if @non_example_exception_count == 0
|
48
|
+
begin
|
49
|
+
RSpec.configuration.load_spec_files
|
50
|
+
rescue Exception => e
|
51
|
+
puts "#{e.class}: #{e}"
|
52
|
+
puts e.backtrace.join("\n")
|
53
|
+
@non_example_exception_count = 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
if @non_example_exception_count == 0
|
57
|
+
@non_example_exception_count = RSpec.world.reporter.non_example_exception_count
|
58
|
+
end
|
59
|
+
@queue = []
|
60
|
+
if @non_example_exception_count == 0
|
61
|
+
@queue += RSpecFacade.all_example_groups
|
62
|
+
puts "#{@queue.length} example groups queued"
|
63
|
+
else
|
64
|
+
puts "#{@non_example_exception_count} errors outside of examples, aborting"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
attr :non_example_exception_count
|
69
|
+
|
70
|
+
def run
|
71
|
+
Thread.new do
|
72
|
+
#HttpServer.set(:master, self).run!(port: 6031)
|
73
|
+
MsgpackServer.new(self).run
|
74
|
+
end
|
75
|
+
until @stop
|
76
|
+
sleep 1
|
77
|
+
end
|
78
|
+
Paraspec.logger.debug_state("Exiting")
|
79
|
+
end
|
80
|
+
|
81
|
+
def ping
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def stop
|
86
|
+
Paraspec.logger.debug_state("Stopping")
|
87
|
+
@stop = true
|
88
|
+
end
|
89
|
+
|
90
|
+
def stop?
|
91
|
+
@stop
|
92
|
+
end
|
93
|
+
|
94
|
+
def suite_ok?
|
95
|
+
RSpec.configuration.reporter.send(:instance_variable_get,'@non_example_exception_count') == 0
|
96
|
+
end
|
97
|
+
|
98
|
+
def example_count
|
99
|
+
RSpecFacade.all_examples.count
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_spec
|
103
|
+
while true
|
104
|
+
example_group = @queue.shift
|
105
|
+
return nil if example_group.nil?
|
106
|
+
|
107
|
+
# TODO I am still not 100% on what should be filtered and pruned where,
|
108
|
+
# but we shouldn't be returning a specification here unless
|
109
|
+
# there are tests in it that a worker will run
|
110
|
+
pruned_examples = RSpec.configuration.filter_manager.prune(example_group.examples)
|
111
|
+
next if pruned_examples.empty?
|
112
|
+
|
113
|
+
m = example_group.metadata
|
114
|
+
return {
|
115
|
+
file_path: m[:file_path],
|
116
|
+
scoped_id: m[:scoped_id],
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def example_passed(payload)
|
122
|
+
spec = payload[:spec]
|
123
|
+
# ExecutionResult
|
124
|
+
result = payload['result']
|
125
|
+
do_example_passed(spec, result)
|
126
|
+
end
|
127
|
+
|
128
|
+
def notify_example_started(payload)
|
129
|
+
example = find_example(payload[:spec])
|
130
|
+
reporter.example_started(example)
|
131
|
+
end
|
132
|
+
|
133
|
+
def do_example_passed(spec, execution_result)
|
134
|
+
#return
|
135
|
+
example = find_example(spec)
|
136
|
+
# Can write to example here
|
137
|
+
example.metadata[:execution_result] = execution_result
|
138
|
+
status = execution_result.status
|
139
|
+
m = "example_#{status}"
|
140
|
+
reporter.send(m, example)
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
def find_example(spec)
|
145
|
+
if spec.nil?
|
146
|
+
#byebug
|
147
|
+
raise ArgumentError, 'Nil spec'
|
148
|
+
end
|
149
|
+
example = (RSpecFacade.all_example_groups + RSpecFacade.all_examples).detect do |example|
|
150
|
+
example.metadata[:file_path] == spec[:file_path] &&
|
151
|
+
example.metadata[:scoped_id] == spec[:scoped_id]
|
152
|
+
end
|
153
|
+
unless example
|
154
|
+
puts "Not found: #{spec[:file_path]}[#{spec[:scoped_id]}]"
|
155
|
+
#byebug
|
156
|
+
raise "Not found: #{spec[:file_path]}[#{spec[:scoped_id]}]"
|
157
|
+
end
|
158
|
+
example
|
159
|
+
end
|
160
|
+
|
161
|
+
def reporter
|
162
|
+
@reporter ||= RSpec.configuration.reporter
|
163
|
+
end
|
164
|
+
|
165
|
+
def suite_started
|
166
|
+
@start_time = Time.now
|
167
|
+
|
168
|
+
notification = RSpec::Core::Notifications::StartNotification.new(
|
169
|
+
RSpecFacade.all_examples.count, 0
|
170
|
+
)
|
171
|
+
RSpec.configuration.formatters.each do |f|
|
172
|
+
if f.respond_to?(:start)
|
173
|
+
f.start(notification)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
true
|
178
|
+
end
|
179
|
+
|
180
|
+
def dump_summary
|
181
|
+
reporter.stop
|
182
|
+
|
183
|
+
all_examples = RSpecFacade.all_examples
|
184
|
+
notification = RSpec::Core::Notifications::SummaryNotification.new(
|
185
|
+
@start_time ? Time.now-@start_time : 0,
|
186
|
+
all_examples,
|
187
|
+
all_examples.select { |e| e.execution_result.status == :failed },
|
188
|
+
all_examples.select { |e| e.execution_result.status == :pending },
|
189
|
+
0,
|
190
|
+
non_example_exception_count,
|
191
|
+
)
|
192
|
+
examples_notification = RSpec::Core::Notifications::ExamplesNotification.new(reporter)
|
193
|
+
RSpec.configuration.formatters.each do |f|
|
194
|
+
if f.respond_to?(:dump_summary)
|
195
|
+
f.dump_summary(notification)
|
196
|
+
end
|
197
|
+
if f.respond_to?(:dump_failures)
|
198
|
+
f.dump_failures(examples_notification)
|
199
|
+
end
|
200
|
+
if f.respond_to?(:dump_pending)
|
201
|
+
f.dump_pending(examples_notification)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
nil
|
205
|
+
end
|
206
|
+
|
207
|
+
def status
|
208
|
+
if RSpecFacade.all_examples.any? { |example| example.execution_result.status == :failed }
|
209
|
+
1
|
210
|
+
else
|
211
|
+
0
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def ident
|
216
|
+
"[m]"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|