Module: RSpec::Abq

Defined in:
lib/rspec/abq.rb,
lib/rspec/abq/version.rb,
lib/rspec/abq/manifest.rb,
lib/rspec/abq/ordering.rb,
lib/rspec/abq/formatter.rb,
lib/rspec/abq/test_case.rb,
lib/rspec/abq/extensions.rb

Overview

An abq adapter for RSpec!

Defined Under Namespace

Modules: Extensions, Manifest, Ordering Classes: Formatter, TestCase

Constant Summary collapse

Error =

Base ABQ error.

Class.new(StandardError)
UnsupportedConfigurationError =

raised when check_configuration fails

Class.new(Error)
ConnectionFailed =

Failed to connect and initialize handshake.

Class.new(Error)
ConnectionBroken =

Communication between abq sockets follows the following protocol:

- The first 4 bytes an unsigned 32-bit integer (big-endian) representing
  the size of the rest of the message.
- The rest of the message is a JSON-encoded payload.
Class.new(Error)
VERSION =

current version!

"1.2.0"

Class Method Summary collapse

Class Method Details

.check_configuration!(config) ⇒ Object

raises if RSpec is configured in a way that’s incompatible with rspec-abq



168
169
170
171
172
173
174
# File 'lib/rspec/abq.rb', line 168

def self.check_configuration!(config)
  if config.fail_fast
    warn("ERROR:\trspec-abq doesn't presently support running with fail-fast enabled.\n" \
               "\tplease disable fail-fast and try again.")
    fail UnsupportedConfigurationError, "unsupported fail-fast detected."
  end
end

.configure_rspec!Object

This is called from World#ordered_example_group and is used to configure rspec based on

  1. rspec-abq expected defaults

  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)



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/rspec/abq.rb', line 180

def self.configure_rspec!
  return if @rspec_configured
  @rspec_configured = true

  check_configuration!(RSpec.configuration)
  # ABQ doesn't support writing example status to disk yet.
  # in its simple implementation, status persistance write the status of all tests which ends up hanging under
  # abq because we haven't run most of the tests in @example_group. (maybe the hanging is rspec trying to execute the tests?).
  # In any case: it's disabled.
  # we set this even if the manifest is being generated
  RSpec.configuration.example_status_persistence_file_path = nil

  # if we're generating a manifest, we don't want to do any other setup
  return if !!ENV[ABQ_GENERATE_MANIFEST]

  # after the manfiest has been sent to the worker, the rspec process will quit and the workers will each start a
  # new rspec process

  # enabling colors allows us to pass through nicer error messages
  if Gem::Version.new(RSpec::Core::Version::STRING) >= Gem::Version.new("3.6.0")
    RSpec.configuration.color_mode = :on
  else
    RSpec.configuration.color = true
    RSpec.configuration.tty = true
  end

  # RSpec only adds a default formatter if there are no formatters.
  # Abq::Formatter is used for internal communication over the ABQ protocol, not for $stdout.
  RSpec.configuration.add_formatter(RSpec.configuration.default_formatter) if RSpec.configuration.formatters.empty?

  RSpec.configuration.add_formatter(RSpec::Abq::Formatter)

  # the first message is the init_meta block of the manifest. This is used to share runtime configuration
  # information amongst worker processes. In RSpec, it is used to ensure that random ordering between workers
  # shares the same seed.
  init_message = protocol_read
  protocol_write(INIT_SUCCESS_MESSAGE)

  if init_message["fast_exit"]
    @fast_exit = true
    return
  end

  Ordering.setup!(init_message["init_meta"], RSpec.configuration)
  nil
end

.disable_tests_when_run_by_abq?Boolean

Disables tests so we can compare runtime of rspec core vs parallelized version. Additionally, disables tests if forced via ABQ_DISABLE_TESTS env var.

Returns:

  • (Boolean)


138
139
140
141
# File 'lib/rspec/abq.rb', line 138

def self.disable_tests_when_run_by_abq?
  enabled? ||
    ENV.key?("ABQ_DISABLE_TESTS")
end

.enabled?(env = ENV) ⇒ Boolean

Whether this rspec process is running in ABQ mode.

Returns:

  • (Boolean)


125
126
127
128
129
130
131
132
133
# File 'lib/rspec/abq.rb', line 125

def self.enabled?(env = ENV)
  if env.key?(ABQ_SOCKET) # is rspec being called from abq?
    env[ABQ_RSPEC_PID] ||= Process.pid.to_s # set the pid of the native runner
    env[ABQ_RSPEC_PID] == Process.pid.to_s # and ensure the pid is this process
    # we check the pid to guard against nested rspec calls thinking they're being called from abq
  else
    false
  end
end

.protocol_read(socket = Abq.socket) ⇒ Object

Writes a message to an Abq socket using the 4-byte header protocol.

Parameters:

  • socket (TCPSocket) (defaults to: Abq.socket)

Returns:

  • msg



290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/rspec/abq.rb', line 290

def self.protocol_read(socket = Abq.socket)
  len_bytes = socket.read 4
  return :abq_done if len_bytes.nil?

  len = len_bytes.unpack1("N")
  json_msg = socket.read len
  return :abq_done if json_msg.nil?

  JSON.parse json_msg
rescue SystemCallError, IOError
  raise ConnectionBroken
rescue
  raise Error
end

.protocol_write(msg, socket = Abq.socket) ⇒ Object

Writes a message to an Abq socket using the 4-byte header protocol.

Parameters:

  • socket (TCPSocket) (defaults to: Abq.socket)
  • msg


276
277
278
279
280
281
282
283
284
# File 'lib/rspec/abq.rb', line 276

def self.protocol_write(msg, socket = Abq.socket)
  json_msg = JSON.dump msg
  socket.write [json_msg.bytesize].pack("N")
  socket.write json_msg
rescue SystemCallError, IOError
  raise ConnectionBroken
rescue
  raise Error
end