rspec-abq 1.0.4 → 1.0.6
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 +4 -4
- data/README.md +34 -1
- data/lib/rspec/abq/extensions.rb +41 -21
- 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 +115 -3
- 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: 9125ceba8d09d6d41098e903710cdfd0a2396dcd3bdee90b920d92d77797d70c
|
4
|
+
data.tar.gz: b8af9112ba33b1ad2d17bb7990d3a462082ec4b570b8ac0ac9117ff5bfd2ee4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91b617a1aff48d2d3f226689e1e0e1d887bfe2eb26cfc04052ab58879c96e32f71436e40a18ccdbb0f59ebb0b89bcdad52a85291a885db9c20f4371df9b66ff4
|
7
|
+
data.tar.gz: 5e58333ce3e0ae2e810ac8e412dc8c4632815439c84716961ecb6dd56869f9798b2ce191f095616cf5173d7b5278787c6971e551a52a37f2382ce6ffd7cd0c16
|
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
|
@@ -85,12 +87,12 @@ module RSpec
|
|
85
87
|
# be sent to us from ABQ, we now loop over all the examples, and mark
|
86
88
|
# every one that we must run in this group as a failure.
|
87
89
|
for_filtered_examples(reporter) do |example|
|
88
|
-
|
89
|
-
|
90
|
-
|
90
|
+
if Abq.target_test_case.is_example?(example)
|
91
|
+
example.fail_with_exception(reporter, ex)
|
92
|
+
Abq.fetch_next_example
|
93
|
+
end
|
91
94
|
end
|
92
95
|
|
93
|
-
RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met?
|
94
96
|
false
|
95
97
|
ensure
|
96
98
|
if Gem::Version.new(RSpec::Core::Version::STRING) >= Gem::Version.new("3.11.0")
|
@@ -106,21 +108,26 @@ module RSpec
|
|
106
108
|
module Runner
|
107
109
|
# Runs the provided example groups.
|
108
110
|
#
|
109
|
-
# @param
|
111
|
+
# @param example_groups [Array<RSpec::Core::ExampleGroup>] groups to run.
|
110
112
|
# Ignored in favor of @world.ordered_example_groups
|
111
|
-
# @return [Fixnum] exit status code. 0 if all specs passed,
|
112
|
-
# or the configured failure exit
|
113
|
+
# @return [Fixnum] exit status code. 0 if all specs passed or if rspec-abq wants to quit early,
|
114
|
+
# or the configured failure exit status (1 by default) if specs
|
113
115
|
# failed.
|
114
|
-
def run_specs(
|
115
|
-
|
116
|
-
|
116
|
+
def run_specs(example_groups)
|
117
|
+
if !!ENV[ABQ_GENERATE_MANIFEST]
|
118
|
+
# before abq can start workers, it asks for a manifest
|
119
|
+
RSpec::Abq::Manifest.write_manifest(example_groups, RSpec.configuration.seed, RSpec.configuration.ordering_registry)
|
120
|
+
# gracefully quit after manifest generation. The worker will launch another instance of rspec with an init_message
|
121
|
+
return 0
|
122
|
+
end
|
123
|
+
|
124
|
+
return 0 if Abq.fast_exit?
|
117
125
|
|
118
|
-
#
|
119
|
-
|
120
|
-
# https://github.com/rspec/rspec-core/blob/522b7727d02d9648c090b56fa68bbdc18a21c04d/lib/rspec/core/runner.rb#L85-L92
|
121
|
-
# So this definition is safe.
|
122
|
-
example_groups = @world.ordered_example_groups
|
126
|
+
# if not quitting early, ensure we have an initial test
|
127
|
+
Abq.fetch_next_example
|
123
128
|
|
129
|
+
# Note: this is all examples, not the examples run by ABQ. Because of that, the numbers in the worker
|
130
|
+
# summary will likely be wrong.
|
124
131
|
examples_count = @world.example_count(example_groups)
|
125
132
|
examples_passed = @configuration.reporter.report(examples_count) do |reporter|
|
126
133
|
@configuration.with_suite_hooks do
|
@@ -134,13 +141,13 @@ module RSpec
|
|
134
141
|
|
135
142
|
if Abq.target_test_case != Abq::TestCase.end_marker
|
136
143
|
warn "Hit end of test run without being on end marker. Target test case is #{Abq.target_test_case.inspect}"
|
137
|
-
|
144
|
+
examples_passed = false
|
138
145
|
end
|
139
146
|
|
140
147
|
if Gem::Version.new(RSpec::Core::Version::STRING) >= Gem::Version.new("3.10.0")
|
141
148
|
exit_code(examples_passed)
|
142
149
|
else
|
143
|
-
|
150
|
+
examples_passed ? 0 : @configuration.failure_exit_code
|
144
151
|
end
|
145
152
|
end
|
146
153
|
|
@@ -152,6 +159,19 @@ module RSpec
|
|
152
159
|
end
|
153
160
|
end
|
154
161
|
end
|
162
|
+
|
163
|
+
# RSpec uses this for global data that's not configuration
|
164
|
+
module World
|
165
|
+
# we call configure_rspec in #ordered_example_groups because it is called
|
166
|
+
# 1. AFTER all specs are loaded.
|
167
|
+
# We need to call it after all specs are loaded because we want to potentially overwrite config set in the specs
|
168
|
+
# 2. BEFORE any specs are run.
|
169
|
+
# We want to call it before any specs are run because the config we set may affect spec ordering.
|
170
|
+
def ordered_example_groups
|
171
|
+
RSpec::Abq.configure_rspec!
|
172
|
+
super
|
173
|
+
end
|
174
|
+
end
|
155
175
|
end
|
156
176
|
end
|
157
177
|
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.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ayaz Hafiz
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-01-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec-core
|
@@ -45,6 +45,118 @@ 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: nokogiri
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.13.10
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.13.10
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: webdrivers
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 5.2.0
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 5.2.0
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: rack
|
120
|
+
requirement: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 2.2.5
|
125
|
+
type: :development
|
126
|
+
prerelease: false
|
127
|
+
version_requirements: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 2.2.5
|
132
|
+
- !ruby/object:Gem::Dependency
|
133
|
+
name: puma
|
134
|
+
requirement: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 5.6.5
|
139
|
+
type: :development
|
140
|
+
prerelease: false
|
141
|
+
version_requirements: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 5.6.5
|
146
|
+
- !ruby/object:Gem::Dependency
|
147
|
+
name: capybara-inline-screenshot
|
148
|
+
requirement: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 2.2.1
|
153
|
+
type: :development
|
154
|
+
prerelease: false
|
155
|
+
version_requirements: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 2.2.1
|
48
160
|
description: RSpec::Abq is an rspec plugin that replaces its ordering with one that
|
49
161
|
is controlled by abq. It allows for parallelization of rspec on a single machine
|
50
162
|
or across multiple workers.
|
@@ -59,9 +171,9 @@ files:
|
|
59
171
|
- README.md
|
60
172
|
- lib/rspec/abq.rb
|
61
173
|
- lib/rspec/abq/extensions.rb
|
174
|
+
- lib/rspec/abq/formatter.rb
|
62
175
|
- lib/rspec/abq/manifest.rb
|
63
176
|
- lib/rspec/abq/ordering.rb
|
64
|
-
- lib/rspec/abq/reporter.rb
|
65
177
|
- lib/rspec/abq/test_case.rb
|
66
178
|
- lib/rspec/abq/version.rb
|
67
179
|
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
|