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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc4f520d1b869e03a972436e4b3eeddb42ce1c3a88db2b944e7ce8d654ac6c25
4
- data.tar.gz: 8608c3bc156d2789596d3b8606aa7d5a164ddbefce1d58ca1243dddf9a006bb6
3
+ metadata.gz: 42664ef402b6b7ca57d62eaf8be91cc58397b11204213c8a41fb5b51e40bf5ff
4
+ data.tar.gz: 7d4e59e293203b8a061699633518612ab07a46dd1b04e26be85d5362b87705ee
5
5
  SHA512:
6
- metadata.gz: ac6d181459d36293e661b141f2c0a685ed9a40feb70a877f0fb9b645ba58e4e1d73397c7e1c264545709df3ef610ee42170cf4b1a703d05dc0f50c53f919bb33
7
- data.tar.gz: fc5e6cc4d5435fd27552de89b6bfd1cf0a59a077b5f571c20c44788638dbcfb8a9e5cc449338b87a10dc9ccf5b9b8f5b25efb031e16f36d7d8c9de8e887a1203
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 rubies 2.6-3.1 and rspecs 3.5-3.12
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
 
@@ -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
- result = Abq.send_test_result_and_advance { |abq_reporter| considered_example.run(instance, abq_reporter) }
45
- all_examples_succeeded &&= result
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
- Abq.send_test_result_and_advance { |abq_reporter| example.fail_with_exception(abq_reporter, ex) }
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
- # @return [Fixnum] exit status code. 0 if all specs passed,
111
- # or the configured failure exit code (1 by default) if specs
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
- should_quit = RSpec::Abq.setup_after_specs_loaded!
115
- return 0 if should_quit
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
- example_passed = false
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
- example_passed ? 0 : @configuration.failure_exit_code
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
@@ -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 = Abq.extract_metadata_and_tags(example.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 = Abq.extract_metadata_and_tags(group.metadata)
54
+ tags, metadata = extract_metadata_and_tags(group.metadata)
42
55
  {
43
56
  type: "group",
44
57
  name: group.id,
@@ -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
- configuration.seed = init_meta["seed"]
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
- registry.register(:global, ordering_from_manifest)
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
@@ -1,6 +1,6 @@
1
1
  module RSpec
2
2
  module Abq
3
3
  # current version!
4
- VERSION = "1.0.3"
4
+ VERSION = "1.0.5"
5
5
  end
6
6
  end
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
- require_relative "abq/extensions"
6
- require_relative "abq/manifest"
7
- require_relative "abq/ordering"
8
- require_relative "abq/reporter"
9
- require_relative "abq/test_case"
10
- require_relative "abq/version"
11
-
12
- # We nest our patch into RSpec's module -- why not?
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: 1
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) && # this is the basic check for rspec being called from an abq worker
71
- (!env.key?(ABQ_RSPEC_PID) || env[ABQ_RSPEC_PID] == Process.pid.to_s) # and this check ensures that any _nested_ processes do not communicate with the worker.
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.setup!
146
+ def self.setup_extensions_if_enabled!
86
147
  return unless enabled?
87
148
  Extensions.setup!
88
149
  end
89
150
 
90
- # raised if we try to load rspec-abq twice
91
- # perhaps RSpec or a plugin has changed behavior to break assumptions we've made with rspec-abq
92
- AbqLoadedTwiceError = Class.new(StandardError)
151
+ # raised when check_configuration fails
152
+ UnsupportedConfigurationError = Class.new(StandardError)
93
153
 
94
- # @!visibility private
95
- # @return [Boolean]
96
- def self.setup_after_specs_loaded!
97
- fail AbqLoadedTwiceError, "tried to setup abq-rspec twice" if ENV[ABQ_RSPEC_PID]
98
- ENV[ABQ_RSPEC_PID] ||= Process.pid.to_s
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 with under
102
- # abq because we haven't run most of the tests in this worker. (maybe it's running the tests?). In any case:
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
- # before abq can start workers, it asks for a manifest
107
- if !!ENV[ABQ_GENERATE_MANIFEST] # the abq worker will set this env var if it needs a 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, so can be deterministic.
196
+ # shares the same seed.
126
197
  init_message = protocol_read
127
198
  protocol_write(INIT_SUCCESS_MESSAGE)
128
- # TODO: delete the check for empty init_meta when https://github.com/rwx-research/abq/pull/216 is merged
129
- return true if init_message["fast_exit"]
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.setup!
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.3
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
@@ -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