Module: RSpec::OpenAPI::ParallelRecords

Defined in:
lib/rspec/openapi/parallel_records.rb

Overview

Collects records across processes when the test framework forks parallel workers, like Rails' parallelize. Records accumulate in each worker's own memory, so without this the main process would write the schema from an empty set. Each worker dumps its records to a shared directory on exit, and the main process merges every dump back in before recording results.

The dumps go through YAML.safe_load with an allowlist rather than Marshal, so reading them cannot instantiate arbitrary objects even if the directory were tampered with.

Constant Summary collapse

MAIN_PID =

The pid of the process that loaded this gem. Forked workers inherit the constant, which is how they know they are not the main process.

Process.pid
RUN_ID =

Unpredictable per-run token. Workers inherit it across fork, while other local users cannot guess it to pre-create or plant files in the dump directory, which lives in the world-writable system tmpdir.

SecureRandom.hex(16)
UPLOADED_FILE_MARKER =

Marks the stand-in a multipart upload leaves in a dump. The schema builder only ever asks an upload for its class and metadata, never for its content, so the file itself does not need to survive the trip.

'__rspec_openapi_uploaded_file'

Class Method Summary collapse

Class Method Details

.dump!Object



50
51
52
53
54
55
56
57
58
# File 'lib/rspec/openapi/parallel_records.rb', line 50

def dump!
  return if RSpec::OpenAPI.path_records.empty?

  FileUtils.mkdir_p(dump_dir, mode: 0o700)
  encoded = RSpec::OpenAPI.path_records.transform_values do |records|
    records.map { |record| transform_record(record) { |value| encode(value) } }
  end
  File.write(File.join(dump_dir, "#{Process.pid}.yaml"), YAML.dump(encoded))
end

.merge!Object



60
61
62
63
64
65
66
67
68
69
70
# File 'lib/rspec/openapi/parallel_records.rb', line 60

def merge!
  Dir.glob(File.join(dump_dir, '*.yaml')).sort.each do |file|
    records_by_path = YAML.safe_load(File.read(file), permitted_classes: permitted_classes, aliases: true)
    records_by_path.each do |path, records|
      decoded = records.map { |record| transform_record(record) { |value| decode(value) } }
      RSpec::OpenAPI.path_records[path].concat(decoded)
    end
  end
ensure
  FileUtils.rm_rf(dump_dir)
end

.schedule_dump!Object

Called when a worker records an example. Registered lazily from inside the worker because an at_exit inherited from the main process has already run there by the time workers fork: minitest executes the whole suite from an at_exit hook, so handlers the gem registered at load time are popped before any fork happens.



43
44
45
46
47
48
# File 'lib/rspec/openapi/parallel_records.rb', line 43

def schedule_dump!
  return if @dump_scheduled || !worker?

  @dump_scheduled = true
  at_exit { dump! }
end

.worker?Boolean

Returns:

  • (Boolean)


34
35
36
# File 'lib/rspec/openapi/parallel_records.rb', line 34

def worker?
  Process.pid != MAIN_PID
end