rspec-abq 1.0.3 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -1
- data/lib/rspec/abq/extensions.rb +39 -13
- data/lib/rspec/abq/formatter.rb +87 -0
- data/lib/rspec/abq/manifest.rb +16 -3
- data/lib/rspec/abq/ordering.rb +14 -2
- data/lib/rspec/abq/version.rb +1 -1
- data/lib/rspec/abq.rb +114 -57
- metadata +100 -2
- data/lib/rspec/abq/reporter.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42664ef402b6b7ca57d62eaf8be91cc58397b11204213c8a41fb5b51e40bf5ff
|
4
|
+
data.tar.gz: 7d4e59e293203b8a061699633518612ab07a46dd1b04e26be85d5362b87705ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecab75285b475df1976cfeb1483475c6dab5cba5bc0dbc6089a4c047f01c3335abb2c4580208e006cbc4df7759a7778c4006d3b5a589f834c58e9b67439f114d
|
7
|
+
data.tar.gz: cf04d645e08f734416c4804d7ccf62fb5337f6ca9cb752226786977a5f6adebe865d78a2f817234bfed34a7bfc80f5fe776102b259b8ff12ddeff2956b12122f
|
data/README.md
CHANGED
@@ -36,7 +36,40 @@ require 'rspec/abq'
|
|
36
36
|
|
37
37
|
## Compatibility
|
38
38
|
|
39
|
-
This gem is actively tested against
|
39
|
+
This gem is actively tested against
|
40
|
+
|
41
|
+
- rubies 2.6-3.1
|
42
|
+
- rspecs 3.5-3.12
|
43
|
+
|
44
|
+
as well as a handful of rspec plugins:
|
45
|
+
|
46
|
+
- rspec-retry 0.6.2
|
47
|
+
- capybara 3.36.0
|
48
|
+
- selenium-webdriver 4.1.0
|
49
|
+
- capybara-inline-screenshot 2.2.1 (see note below)
|
50
|
+
|
51
|
+
### Usage with capybara-inline-screenshot (or capybara-screenshot)
|
52
|
+
|
53
|
+
#### tldr
|
54
|
+
|
55
|
+
1. run rspec with a compatible `--format` option:
|
56
|
+
|
57
|
+
```sh
|
58
|
+
abq test -- bundle exec rspec --format documentation
|
59
|
+
```
|
60
|
+
|
61
|
+
2. check for screenshots in the worker output
|
62
|
+
|
63
|
+
#### What's happening?
|
64
|
+
|
65
|
+
By default, rspec-abq only sends rspec-output to the test process. Because of this, custom formatting (e.g. that
|
66
|
+
done by capybara-inline-screenshot) is lost.
|
67
|
+
|
68
|
+
If you want to enable custom formatting (which will show inline screenshots), enable a
|
69
|
+
capybara-inline-screenshot compatible formatter (`progress` or `documentation`). This will output rspec results
|
70
|
+
(including screenshots) from the worker processeses.
|
71
|
+
|
72
|
+
Note: the test process output will still only have the aggregate results without screenshots.
|
40
73
|
|
41
74
|
## Development
|
42
75
|
|
data/lib/rspec/abq/extensions.rb
CHANGED
@@ -7,6 +7,7 @@ module RSpec
|
|
7
7
|
def self.setup!
|
8
8
|
RSpec::Core::ExampleGroup.extend(ExampleGroup)
|
9
9
|
RSpec::Core::Runner.prepend(Runner)
|
10
|
+
RSpec::Core::World.prepend(World)
|
10
11
|
end
|
11
12
|
|
12
13
|
# ExampleGroups are nodes in a tree with
|
@@ -27,7 +28,7 @@ module RSpec
|
|
27
28
|
# - so we continue iterating until we get there
|
28
29
|
# - or in another ExampleGroup
|
29
30
|
# - so we bail from this iteration and let the caller (run_with_abq) iterate to the right ExampleGroup
|
30
|
-
def run_examples_with_abq
|
31
|
+
def run_examples_with_abq(reporter)
|
31
32
|
all_examples_succeeded = true
|
32
33
|
ordering_strategy.order(filtered_examples).each do |considered_example|
|
33
34
|
next unless Abq.target_test_case.is_example?(considered_example)
|
@@ -41,9 +42,10 @@ module RSpec
|
|
41
42
|
# true &&= expression : expression will be run, fine!
|
42
43
|
# false &&= expression: expression will NOT be run! bad!
|
43
44
|
# we want to always run the test, even if the previous test failed.
|
44
|
-
|
45
|
-
all_examples_succeeded &&=
|
45
|
+
succeeded = considered_example.run(instance, reporter)
|
46
|
+
all_examples_succeeded &&= succeeded
|
46
47
|
|
48
|
+
Abq.fetch_next_example
|
47
49
|
break unless Abq.target_test_case.directly_in_group?(self)
|
48
50
|
end
|
49
51
|
all_examples_succeeded
|
@@ -68,7 +70,7 @@ module RSpec
|
|
68
70
|
# the examples; otherwise, we just need to check the children groups.
|
69
71
|
result_for_this_group =
|
70
72
|
if Abq.target_test_case.directly_in_group?(self)
|
71
|
-
run_examples_with_abq
|
73
|
+
run_examples_with_abq(reporter)
|
72
74
|
else
|
73
75
|
true
|
74
76
|
end
|
@@ -87,10 +89,9 @@ module RSpec
|
|
87
89
|
for_filtered_examples(reporter) do |example|
|
88
90
|
next unless Abq.target_test_case.is_example?(example)
|
89
91
|
|
90
|
-
|
92
|
+
example.fail_with_exception(abq_reporter, ex)
|
91
93
|
end
|
92
94
|
|
93
|
-
RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met?
|
94
95
|
false
|
95
96
|
ensure
|
96
97
|
if Gem::Version.new(RSpec::Core::Version::STRING) >= Gem::Version.new("3.11.0")
|
@@ -106,14 +107,26 @@ module RSpec
|
|
106
107
|
module Runner
|
107
108
|
# Runs the provided example groups.
|
108
109
|
#
|
109
|
-
# @param example_groups [Array<RSpec::Core::ExampleGroup>] groups to run
|
110
|
-
#
|
111
|
-
#
|
110
|
+
# @param example_groups [Array<RSpec::Core::ExampleGroup>] groups to run.
|
111
|
+
# Ignored in favor of @world.ordered_example_groups
|
112
|
+
# @return [Fixnum] exit status code. 0 if all specs passed or if rspec-abq wants to quit early,
|
113
|
+
# or the configured failure exit status (1 by default) if specs
|
112
114
|
# failed.
|
113
115
|
def run_specs(example_groups)
|
114
|
-
|
115
|
-
|
116
|
+
if !!ENV[ABQ_GENERATE_MANIFEST]
|
117
|
+
# before abq can start workers, it asks for a manifest
|
118
|
+
RSpec::Abq::Manifest.write_manifest(example_groups, RSpec.configuration.seed, RSpec.configuration.ordering_registry)
|
119
|
+
# gracefully quit after manifest generation. The worker will launch another instance of rspec with an init_message
|
120
|
+
return 0
|
121
|
+
end
|
122
|
+
|
123
|
+
return 0 if Abq.fast_exit?
|
124
|
+
|
125
|
+
# if not quitting early, ensure we have an initial test
|
126
|
+
Abq.fetch_next_example
|
116
127
|
|
128
|
+
# Note: this is all examples, not the examples run by ABQ. Because of that, the numbers in the worker
|
129
|
+
# summary will likely be wrong.
|
117
130
|
examples_count = @world.example_count(example_groups)
|
118
131
|
examples_passed = @configuration.reporter.report(examples_count) do |reporter|
|
119
132
|
@configuration.with_suite_hooks do
|
@@ -127,13 +140,13 @@ module RSpec
|
|
127
140
|
|
128
141
|
if Abq.target_test_case != Abq::TestCase.end_marker
|
129
142
|
warn "Hit end of test run without being on end marker. Target test case is #{Abq.target_test_case.inspect}"
|
130
|
-
|
143
|
+
examples_passed = false
|
131
144
|
end
|
132
145
|
|
133
146
|
if Gem::Version.new(RSpec::Core::Version::STRING) >= Gem::Version.new("3.10.0")
|
134
147
|
exit_code(examples_passed)
|
135
148
|
else
|
136
|
-
|
149
|
+
examples_passed ? 0 : @configuration.failure_exit_code
|
137
150
|
end
|
138
151
|
end
|
139
152
|
|
@@ -145,6 +158,19 @@ module RSpec
|
|
145
158
|
end
|
146
159
|
end
|
147
160
|
end
|
161
|
+
|
162
|
+
# RSpec uses this for global data that's not configuration
|
163
|
+
module World
|
164
|
+
# we call configure_rspec in #ordered_example_groups because it is called
|
165
|
+
# 1. AFTER all specs are loaded.
|
166
|
+
# We need to call it after all specs are loaded because we want to potentially overwrite config set in the specs
|
167
|
+
# 2. BEFORE any specs are run.
|
168
|
+
# We want to call it before any specs are run because the config we set may affect spec ordering.
|
169
|
+
def ordered_example_groups
|
170
|
+
RSpec::Abq.configure_rspec!
|
171
|
+
super
|
172
|
+
end
|
173
|
+
end
|
148
174
|
end
|
149
175
|
end
|
150
176
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
if Gem::Version.new(RSpec::Core::Version::STRING) < Gem::Version.new("3.6.0")
|
4
|
+
# addresses this issue https://github.com/rspec/rspec-core/issues/2471
|
5
|
+
require "rspec/core/formatters/console_codes"
|
6
|
+
end
|
7
|
+
|
8
|
+
module RSpec
|
9
|
+
module Abq
|
10
|
+
# Formatters are used to format RSpec test results. In our case, we're using it to
|
11
|
+
# report test results to the abq socket.
|
12
|
+
class Formatter
|
13
|
+
RSpec::Core::Formatters.register self, :example_passed, :example_pending, :example_failed
|
14
|
+
|
15
|
+
# we don't use the output IO for this formatter (we report everything via the socket)
|
16
|
+
def initialize(_output) # rubocop:disable Lint/RedundantInitialize
|
17
|
+
end
|
18
|
+
|
19
|
+
# called when an example is completed (this method is aliased to example_pending and example_failed)
|
20
|
+
def example_passed(notification)
|
21
|
+
Abq.protocol_write(Formatter.abq_result(notification.example))
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :example_pending, :example_passed
|
25
|
+
alias_method :example_failed, :example_passed
|
26
|
+
|
27
|
+
# takes a completed example and creates a abq-compatible test result
|
28
|
+
def self.abq_result(example)
|
29
|
+
execution_result = example.execution_result
|
30
|
+
tags, meta = Manifest.extract_metadata_and_tags(example.metadata)
|
31
|
+
{
|
32
|
+
test_result: {
|
33
|
+
status: status(example),
|
34
|
+
id: example.id,
|
35
|
+
display_name: example.metadata[:full_description],
|
36
|
+
output: if execution_result.exception
|
37
|
+
RSpec::Core::Formatters::ExceptionPresenter
|
38
|
+
.new(execution_result.exception, example)
|
39
|
+
.fully_formatted(1)
|
40
|
+
end,
|
41
|
+
runtime: (execution_result.run_time * 1_000_000_000).round,
|
42
|
+
tags: tags,
|
43
|
+
meta: meta,
|
44
|
+
location: {
|
45
|
+
file: example.metadata[:file_path],
|
46
|
+
line: example.metadata[:line_number]
|
47
|
+
},
|
48
|
+
started_at: execution_result.started_at.utc.iso8601,
|
49
|
+
finished_at: execution_result.finished_at.utc.iso8601,
|
50
|
+
lineage: RSpec::Core::Metadata.ascend(example.metadata).map { |meta| meta[:description] }.reverse
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
private_class_method def self.status(example)
|
56
|
+
execution_result = example.execution_result
|
57
|
+
exception = execution_result.exception
|
58
|
+
case execution_result.status
|
59
|
+
when :passed
|
60
|
+
{type: :success}
|
61
|
+
when :failed
|
62
|
+
{
|
63
|
+
type:
|
64
|
+
if exception.is_a?(RSpec::Expectations::ExpectationNotMetError)
|
65
|
+
:failure
|
66
|
+
else
|
67
|
+
:error
|
68
|
+
end,
|
69
|
+
exception:
|
70
|
+
if exception.class.name.to_s == ""
|
71
|
+
"(anonymous error class)"
|
72
|
+
else
|
73
|
+
exception.class.name.to_s
|
74
|
+
end,
|
75
|
+
backtrace: RSpec::Core::Formatters::ExceptionPresenter.new(exception, example).formatted_backtrace
|
76
|
+
}
|
77
|
+
when :pending
|
78
|
+
if execution_result.example_skipped?
|
79
|
+
{type: :skipped}
|
80
|
+
else
|
81
|
+
{type: :pending}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/rspec/abq/manifest.rb
CHANGED
@@ -12,13 +12,26 @@ module RSpec
|
|
12
12
|
# @param ordered_groups [Array<RSpec::Core::ExampleGroup>] ordered groups to assemble into a manifest
|
13
13
|
def self.generate(ordered_groups, random_seed, registry)
|
14
14
|
{
|
15
|
+
type: "manifest_success",
|
15
16
|
manifest: {
|
16
17
|
init_meta: RSpec::Abq::Ordering.to_meta(random_seed, registry),
|
17
18
|
members: ordered_groups.map { |group| to_manifest_group(group) }.compact
|
18
|
-
}
|
19
|
+
},
|
20
|
+
other_errors: nil
|
19
21
|
}
|
20
22
|
end
|
21
23
|
|
24
|
+
# Takes group or example metadata and returns a two-element array:
|
25
|
+
# a tag is any piece of metadata that has a value of true
|
26
|
+
# @return [Array<Array<Symbol>, Hash<Symbol, Object>>] tags and metadata
|
27
|
+
# @!visibility private
|
28
|
+
def self.extract_metadata_and_tags(metadata)
|
29
|
+
# we use `.dup.reject! because `.reject` raises a warning (because it doesn't dup procs)`
|
30
|
+
user_metadata = metadata.dup.reject! { |k, _v| RESERVED_METADATA_KEYS.include?(k) }
|
31
|
+
tags_array, metadata_array = user_metadata.partition { |_k, v| v == true }
|
32
|
+
[tags_array.map(&:first), metadata_array.to_h]
|
33
|
+
end
|
34
|
+
|
22
35
|
# @!visibility private
|
23
36
|
# @param group [RSpec::Core::ExampleGroup]
|
24
37
|
private_class_method def self.to_manifest_group(group)
|
@@ -26,7 +39,7 @@ module RSpec
|
|
26
39
|
# because that's how the runner will execute them.
|
27
40
|
members =
|
28
41
|
group.ordering_strategy.order(group.filtered_examples).map { |example|
|
29
|
-
tags, metadata =
|
42
|
+
tags, metadata = extract_metadata_and_tags(example.metadata)
|
30
43
|
{
|
31
44
|
type: "test",
|
32
45
|
id: example.id,
|
@@ -38,7 +51,7 @@ module RSpec
|
|
38
51
|
group.ordering_strategy.order(group.children).map { |child_group| to_manifest_group(child_group) }.compact
|
39
52
|
)
|
40
53
|
return nil if members.empty?
|
41
|
-
tags, metadata =
|
54
|
+
tags, metadata = extract_metadata_and_tags(group.metadata)
|
42
55
|
{
|
43
56
|
type: "group",
|
44
57
|
name: group.id,
|
data/lib/rspec/abq/ordering.rb
CHANGED
@@ -26,13 +26,25 @@ module RSpec
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# takes the meta (prodced in .to_meta) and applies the settings to the current process
|
29
|
+
# @!visibility private
|
30
|
+
# @return [Boolean] returns true if setup! changed anything
|
29
31
|
def self.setup!(init_meta, configuration)
|
30
|
-
|
32
|
+
changed = false
|
33
|
+
if configuration.seed != init_meta["seed"]
|
34
|
+
configuration.seed = init_meta["seed"]
|
35
|
+
changed = true
|
36
|
+
end
|
37
|
+
|
31
38
|
registry = configuration.ordering_registry
|
32
39
|
ordering_from_manifest = registry.fetch(init_meta["ordering"].to_sym) do
|
33
40
|
fail(UnsupportedOrderingError, "can't order based on unknown ordering: `#{init_meta["ordering"]}`")
|
34
41
|
end
|
35
|
-
|
42
|
+
|
43
|
+
if registry.fetch(:global) != ordering_from_manifest
|
44
|
+
registry.register(:global, ordering_from_manifest)
|
45
|
+
changed = true
|
46
|
+
end
|
47
|
+
changed
|
36
48
|
end
|
37
49
|
end
|
38
50
|
end
|
data/lib/rspec/abq/version.rb
CHANGED
data/lib/rspec/abq.rb
CHANGED
@@ -2,14 +2,70 @@ require "set"
|
|
2
2
|
require "rspec/core"
|
3
3
|
require "socket"
|
4
4
|
require "json"
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
#
|
5
|
+
require "rspec/abq/extensions"
|
6
|
+
require "rspec/abq/manifest"
|
7
|
+
require "rspec/abq/ordering"
|
8
|
+
require "rspec/abq/formatter"
|
9
|
+
require "rspec/abq/test_case"
|
10
|
+
require "rspec/abq/version"
|
11
|
+
|
12
|
+
# How does ABQ & its protocol work?
|
13
|
+
# ======================================
|
14
|
+
# This is a reframing of some of https://www.notion.so/rwx/Native-Runner-Protocol-0-2-d992ef3b4fde4289b02244c1b89a8cc7
|
15
|
+
# With pointers to where it is implemented in this codebase.
|
16
|
+
#
|
17
|
+
# One or more abq workers each launch a "native runner" (in this case, rspec).
|
18
|
+
# one of the native runners is designated the one to produce a manifest (via an environmental variable).
|
19
|
+
#
|
20
|
+
# Each native runner is passed a socket address as an environmental variable to communicate with its worker.
|
21
|
+
# The first message sent over the socket is the NATIVE_RUNNER_SPAWNED_MESSAGE, sent from the native runner to the worker.
|
22
|
+
# (see: `Abq.socket`)
|
23
|
+
#
|
24
|
+
# The Manifest
|
25
|
+
# ------------
|
26
|
+
# The manifest is used by the abq supervisor to coordinate workers.
|
27
|
+
# The manifest contains
|
28
|
+
# - a list of all the tests to run. Each worker will receive a slice of these tests.
|
29
|
+
# - native-runner specific information for configuring workers. In RSPec's case -- we send ordering information to ensure
|
30
|
+
# each instance of rspec orders its specs consistently
|
31
|
+
#
|
32
|
+
# The manifest is written in the monkey-patched RSpec::Core::Runner#run_specs method (see `RSpec::Abq::Extensions::Runner`)
|
33
|
+
#
|
34
|
+
# After the manifest has been generated and sent to the worker, that native runner quits and is relaunched fresh as as a
|
35
|
+
# non-manifest-generating worker.
|
36
|
+
#
|
37
|
+
# "Normal" Native Runners
|
38
|
+
# -----------------------
|
39
|
+
# Non-manifest-generating native runners first perform an initialization handshake, fetching init message from the worker,
|
40
|
+
# which either has config information or instructions for abq to quit early.
|
41
|
+
# (see: `Abq.configure_rspec`)
|
42
|
+
#
|
43
|
+
# After that, native runners fetch test cases from the worker, and send back test results.
|
44
|
+
# RSpec will iterate over all tests, but running only those that the worker tells it to run and skipping the rest.
|
45
|
+
# It will quit when all tests have been iterated over.
|
46
|
+
#
|
47
|
+
# Loading rspec-abq
|
48
|
+
# ========================
|
49
|
+
# The gem itself is usually loaded when `require spec_helper.rb` is called (either explicitly via `require rspec/abq` or
|
50
|
+
# implicitly via the Gemfile).
|
51
|
+
# The spec_helper is usually loaded in one of two ways:
|
52
|
+
# - via a `--require spec_helper` in the .rspec file, which means the gem is loaded BEFORE specs are loaded
|
53
|
+
# - or via a `require spec_helper.rb` in the spec itself, which means the gem is loaded WHILE specs are actively being loaded.
|
54
|
+
#
|
55
|
+
# In either case: the manifest cannot be written until AFTER specs are loaded, so all that loading the gem does is
|
56
|
+
# - check if rspec is being run via an abq worker
|
57
|
+
# - and if so, monkey patch rspec to hook in rspec-abq functionality
|
58
|
+
#
|
59
|
+
# Once rspec is patched ...
|
60
|
+
#
|
61
|
+
# First run manifest generation against the native runner on a single worker:
|
62
|
+
# - configure hard-coded rspec settings (Abq.configure_rspec) called from RSpec::Abq::Extensions::World#ordered_example_groups
|
63
|
+
# - generate the manifest via RSpec::Abq::Extensions::Runner#run_specs
|
64
|
+
# (the native runner quits)
|
65
|
+
#
|
66
|
+
# Then run the native runner again on one or more workers:
|
67
|
+
# - configure static rspec settings, plus settings fetched from the init_message in the manifest.
|
68
|
+
# - then fetch and test cases from the worker and send back test results until there are no tests left
|
13
69
|
module RSpec
|
14
70
|
# An abq adapter for RSpec!
|
15
71
|
module Abq
|
@@ -33,7 +89,7 @@ module RSpec
|
|
33
89
|
PROTOCOL_VERSION = {
|
34
90
|
type: "abq_protocol_version",
|
35
91
|
major: 0,
|
36
|
-
minor:
|
92
|
+
minor: 2
|
37
93
|
}
|
38
94
|
|
39
95
|
# The [rspec-abq specification](https://www.notion.so/rwx/ABQ-Worker-Native-Test-Runner-IPC-Interface-0959f5a9144741d798ac122566a3d887#8587ee4fd01e41ec880dcbe212562172).
|
@@ -67,8 +123,13 @@ module RSpec
|
|
67
123
|
# Whether this rspec process is running in ABQ mode.
|
68
124
|
# @return [Boolean]
|
69
125
|
def self.enabled?(env = ENV)
|
70
|
-
env.key?(ABQ_SOCKET)
|
71
|
-
|
126
|
+
if env.key?(ABQ_SOCKET) # is rspec being called from abq?
|
127
|
+
env[ABQ_RSPEC_PID] ||= Process.pid.to_s # set the pid of the native runner
|
128
|
+
env[ABQ_RSPEC_PID] == Process.pid.to_s # and ensure the pid is this process
|
129
|
+
# we check the pid to guard against nested rspec calls thinking they're being called from abq
|
130
|
+
else
|
131
|
+
false
|
132
|
+
end
|
72
133
|
end
|
73
134
|
|
74
135
|
# Disables tests so we can compare runtime of rspec core vs parallelized version. Additionally, disables tests
|
@@ -79,60 +140,78 @@ module RSpec
|
|
79
140
|
ENV.key?("ABQ_DISABLE_TESTS")
|
80
141
|
end
|
81
142
|
|
82
|
-
# This is the main entry point for abq-rspec, and it's called when the gem is loaded
|
143
|
+
# This is the main entry point for abq-rspec, and it's called when the gem is loaded.
|
83
144
|
# @!visibility private
|
84
145
|
# @return [void]
|
85
|
-
def self.
|
146
|
+
def self.setup_extensions_if_enabled!
|
86
147
|
return unless enabled?
|
87
148
|
Extensions.setup!
|
88
149
|
end
|
89
150
|
|
90
|
-
# raised
|
91
|
-
|
92
|
-
AbqLoadedTwiceError = Class.new(StandardError)
|
151
|
+
# raised when check_configuration fails
|
152
|
+
UnsupportedConfigurationError = Class.new(StandardError)
|
93
153
|
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
154
|
+
# raises if RSpec is configured in a way that's incompatible with rspec-abq
|
155
|
+
def self.check_configuration!(config)
|
156
|
+
if config.fail_fast
|
157
|
+
warn("ERROR:\trspec-abq doesn't presently support running with fail-fast enabled.\n" \
|
158
|
+
"\tplease disable fail-fast and try again.")
|
159
|
+
fail UnsupportedConfigurationError, "unsupported fail-fast detected."
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# This is called from World#ordered_example_group
|
164
|
+
# and is used to configure rspec based on
|
165
|
+
# 1. rspec-abq expected defaults
|
166
|
+
# 2. ordering information sent from the worker (e.g. if the test supervisor has random seed 3, we want this runner to also have the same random seed)
|
167
|
+
def self.configure_rspec!
|
168
|
+
return if @rspec_configured
|
169
|
+
@rspec_configured = true
|
99
170
|
|
171
|
+
check_configuration!(RSpec.configuration)
|
100
172
|
# ABQ doesn't support writing example status to disk yet.
|
101
|
-
# in its simple implementation, status persistance write the status of all tests which ends up hanging
|
102
|
-
# abq because we haven't run most of the tests in
|
103
|
-
# it's disabled.
|
173
|
+
# in its simple implementation, status persistance write the status of all tests which ends up hanging under
|
174
|
+
# abq because we haven't run most of the tests in @example_group. (maybe the hanging is rspec trying to execute the tests?).
|
175
|
+
# In any case: it's disabled.
|
176
|
+
# we set this even if the manifest is being generated
|
104
177
|
RSpec.configuration.example_status_persistence_file_path = nil
|
105
178
|
|
106
|
-
#
|
107
|
-
if !!ENV[ABQ_GENERATE_MANIFEST]
|
108
|
-
RSpec::Abq::Manifest.write_manifest(RSpec.world.ordered_example_groups, RSpec.configuration.seed, RSpec.configuration.ordering_registry)
|
109
|
-
return true
|
110
|
-
end
|
179
|
+
# if we're generating a manifest, we don't want to do any other setup
|
180
|
+
return if !!ENV[ABQ_GENERATE_MANIFEST]
|
111
181
|
|
112
182
|
# after the manfiest has been sent to the worker, the rspec process will quit and the workers will each start a
|
113
183
|
# new rspec process
|
114
184
|
|
115
185
|
# enabling colors allows us to pass through nicer error messages
|
116
|
-
|
117
186
|
if Gem::Version.new(RSpec::Core::Version::STRING) >= Gem::Version.new("3.6.0")
|
118
187
|
RSpec.configuration.color_mode = :on
|
119
188
|
else
|
120
189
|
RSpec.configuration.color = true
|
121
190
|
end
|
122
191
|
|
192
|
+
RSpec.configuration.add_formatter(RSpec::Abq::Formatter)
|
193
|
+
|
123
194
|
# the first message is the init_meta block of the manifest. This is used to share runtime configuration
|
124
195
|
# information amongst worker processes. In RSpec, it is used to ensure that random ordering between workers
|
125
|
-
# shares the same seed
|
196
|
+
# shares the same seed.
|
126
197
|
init_message = protocol_read
|
127
198
|
protocol_write(INIT_SUCCESS_MESSAGE)
|
128
|
-
|
129
|
-
|
199
|
+
|
200
|
+
if init_message["fast_exit"]
|
201
|
+
@fast_exit = true
|
202
|
+
return
|
203
|
+
end
|
130
204
|
|
131
205
|
Ordering.setup!(init_message["init_meta"], RSpec.configuration)
|
132
|
-
fetch_next_example
|
133
206
|
nil
|
134
207
|
end
|
135
208
|
|
209
|
+
# @!visibility private
|
210
|
+
# @return [Boolean]
|
211
|
+
def self.fast_exit?
|
212
|
+
@fast_exit ||= false
|
213
|
+
end
|
214
|
+
|
136
215
|
# Creates the socket to communicate with the worker and sends the worker the protocol
|
137
216
|
# @!visibility private
|
138
217
|
def self.socket
|
@@ -149,17 +228,6 @@ module RSpec
|
|
149
228
|
# @!visibility private
|
150
229
|
RESERVED_METADATA_KEYS = Set.new(RSpec::Core::Metadata::RESERVED_KEYS + [:if, :unless])
|
151
230
|
|
152
|
-
# Takes group or example metadata and returns a two-element array:
|
153
|
-
# a tag is any piece of metadata that has a value of true
|
154
|
-
# @return [Array<Array<Symbol>, Hash<Symbol, Object>>] tags and metadata
|
155
|
-
# @!visibility private
|
156
|
-
def self.extract_metadata_and_tags(metadata)
|
157
|
-
# we use `.dup.reject! because `.reject` raises a warning (because it doesn't dup procs)`
|
158
|
-
user_metadata = metadata.dup.reject! { |k, _v| RESERVED_METADATA_KEYS.include?(k) }
|
159
|
-
tags_array, metadata_array = user_metadata.partition { |_k, v| v == true }
|
160
|
-
[tags_array.map(&:first), metadata_array.to_h]
|
161
|
-
end
|
162
|
-
|
163
231
|
class << self
|
164
232
|
# the target_test_case is the test case the abq worker wants results for
|
165
233
|
# @!visibility private
|
@@ -210,18 +278,7 @@ module RSpec
|
|
210
278
|
return :abq_done if json_msg.nil?
|
211
279
|
JSON.parse json_msg
|
212
280
|
end
|
213
|
-
|
214
|
-
# sends test results to ABQ and advances by one
|
215
|
-
# @!visibility private
|
216
|
-
def self.send_test_result_and_advance(&block)
|
217
|
-
reporter = Reporter.new
|
218
|
-
test_succeeded = block.call(reporter)
|
219
|
-
protocol_write(reporter.abq_result)
|
220
|
-
fetch_next_example
|
221
|
-
# return whether the test succeeded or not
|
222
|
-
test_succeeded
|
223
|
-
end
|
224
281
|
end
|
225
282
|
end
|
226
283
|
|
227
|
-
RSpec::Abq.
|
284
|
+
RSpec::Abq.setup_extensions_if_enabled!
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-abq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ayaz Hafiz
|
@@ -45,6 +45,104 @@ dependencies:
|
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 0.14.1
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec-retry
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.2
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.6.2
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: capybara
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.36.0
|
69
|
+
type: :development
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.36.0
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: selenium-webdriver
|
78
|
+
requirement: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 4.1.0
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.1.0
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: webdrivers
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 5.2.0
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 5.2.0
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: rack
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.2.5
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.2.5
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: puma
|
120
|
+
requirement: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 5.6.5
|
125
|
+
type: :development
|
126
|
+
prerelease: false
|
127
|
+
version_requirements: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 5.6.5
|
132
|
+
- !ruby/object:Gem::Dependency
|
133
|
+
name: capybara-inline-screenshot
|
134
|
+
requirement: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 2.2.1
|
139
|
+
type: :development
|
140
|
+
prerelease: false
|
141
|
+
version_requirements: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 2.2.1
|
48
146
|
description: RSpec::Abq is an rspec plugin that replaces its ordering with one that
|
49
147
|
is controlled by abq. It allows for parallelization of rspec on a single machine
|
50
148
|
or across multiple workers.
|
@@ -59,9 +157,9 @@ files:
|
|
59
157
|
- README.md
|
60
158
|
- lib/rspec/abq.rb
|
61
159
|
- lib/rspec/abq/extensions.rb
|
160
|
+
- lib/rspec/abq/formatter.rb
|
62
161
|
- lib/rspec/abq/manifest.rb
|
63
162
|
- lib/rspec/abq/ordering.rb
|
64
|
-
- lib/rspec/abq/reporter.rb
|
65
163
|
- lib/rspec/abq/test_case.rb
|
66
164
|
- lib/rspec/abq/version.rb
|
67
165
|
homepage: https://github.com/rwx-research/rspec-abq
|
data/lib/rspec/abq/reporter.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
module RSpec
|
2
|
-
module Abq
|
3
|
-
# Realistically we should instead extend [RSpec::Core::Reporter], but
|
4
|
-
# there's some indirection there I don't yet understand, so instead just
|
5
|
-
# build it up from scratch for now.
|
6
|
-
class Reporter
|
7
|
-
attr_reader :status
|
8
|
-
attr_reader :id
|
9
|
-
attr_reader :display_name
|
10
|
-
attr_reader :tags
|
11
|
-
attr_reader :meta
|
12
|
-
|
13
|
-
# @param example [RSpec::Core::Example]
|
14
|
-
def example_started(example)
|
15
|
-
@id = example.id
|
16
|
-
@example = example
|
17
|
-
@display_name = example.metadata[:full_description]
|
18
|
-
@tags, @meta = Abq.extract_metadata_and_tags(example.metadata)
|
19
|
-
end
|
20
|
-
|
21
|
-
# @param example [RSpec::Core::Example]
|
22
|
-
def example_finished(example)
|
23
|
-
@execution_result = example.metadata[:execution_result]
|
24
|
-
end
|
25
|
-
|
26
|
-
# @param example [RSpec::Core::Example]
|
27
|
-
def example_failed(example)
|
28
|
-
@status =
|
29
|
-
if example.execution_result.exception.is_a? RSpec::Expectations::ExpectationNotMetError
|
30
|
-
:failure
|
31
|
-
else
|
32
|
-
:error
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# @param _example [RSpec::Core::Example]
|
37
|
-
def example_passed(_example)
|
38
|
-
@status = :success
|
39
|
-
end
|
40
|
-
|
41
|
-
# @param example [RSpec::Core::Example]
|
42
|
-
def example_pending(example)
|
43
|
-
@status =
|
44
|
-
if example.execution_result.example_skipped?
|
45
|
-
:skipped
|
46
|
-
else
|
47
|
-
:pending
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# @return [String, nil]
|
52
|
-
def output
|
53
|
-
if @execution_result.exception
|
54
|
-
presenter = RSpec::Core::Formatters::ExceptionPresenter.new @execution_result.exception, @example
|
55
|
-
return presenter.fully_formatted 1
|
56
|
-
end
|
57
|
-
nil
|
58
|
-
end
|
59
|
-
|
60
|
-
# @return Int
|
61
|
-
def runtime_ms
|
62
|
-
@execution_result.run_time * 1000
|
63
|
-
end
|
64
|
-
|
65
|
-
# does nothing, just here to fulfill reporter api
|
66
|
-
def example_group_finished(_)
|
67
|
-
end
|
68
|
-
|
69
|
-
# creates a hash that fits the abq worker result protocol
|
70
|
-
def abq_result
|
71
|
-
{
|
72
|
-
test_result: {
|
73
|
-
status: status,
|
74
|
-
id: id,
|
75
|
-
display_name: display_name,
|
76
|
-
output: output,
|
77
|
-
runtime: runtime_ms,
|
78
|
-
tags: tags,
|
79
|
-
meta: meta
|
80
|
-
}
|
81
|
-
}
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|