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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32d3a96e24adf90ae8f04464daccb1458833022b7888eb9f0eb13d4f735cd186
4
- data.tar.gz: 5aa909ff347fb5522a8716028bfc5f187355792e60392143c22bfa097017f4b3
3
+ metadata.gz: 9125ceba8d09d6d41098e903710cdfd0a2396dcd3bdee90b920d92d77797d70c
4
+ data.tar.gz: b8af9112ba33b1ad2d17bb7990d3a462082ec4b570b8ac0ac9117ff5bfd2ee4d
5
5
  SHA512:
6
- metadata.gz: 31765589e5cebda9567807ed17bcafa96be05d285b8d757e9611a5a77230a6a60ed5163dae008cfbc4a2d9228218639b7999181b2e67d11dfde6848b1791d604
7
- data.tar.gz: 9a983dec557fb2f606bf68480b817cc4e75b3d5e318f9c0ed8af56bbaa1362e11706a27d7107788fd45fd1f9808b47f394ee3813132018916295946ec1595edb
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 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
@@ -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
- next unless Abq.target_test_case.is_example?(example)
89
-
90
- Abq.send_test_result_and_advance { |abq_reporter| example.fail_with_exception(abq_reporter, ex) }
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 _example_groups [Array<RSpec::Core::ExampleGroup>] groups to run.
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 code (1 by default) if specs
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(_example_groups)
115
- should_quit = RSpec::Abq.setup_after_specs_loaded!
116
- return 0 if should_quit
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
- # rspec-abq pulls the ordering from the init-message. Here we ensure the example groups are in the same ordering.
119
- # RSpec passes to `run_specs` exactly the world ordered example groups:
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
- example_passed = false
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
- example_passed ? 0 : @configuration.failure_exit_code
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
@@ -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.4"
4
+ VERSION = "1.0.6"
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.4
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: 2022-12-12 00:00:00.000000000 Z
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
@@ -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