Class: Minitest::ForkExecutor

Inherits:
Object
  • Object
show all
Defined in:
lib/minitest/fork_executor.rb

Overview

Minitest runs individual test cases via Minitest.run_one_method. When ForkExecutor is started, we need to override it and implement the fork algorithm. See the detailed comments below to understand how it’s done.

Please keep in mind we support Ruby 1.9 and hence can’t use conveniences offered by more modern Rubies.

Defined Under Namespace

Classes: FailureTransport, UnmarshallableError

Instance Method Summary collapse

Instance Method Details

#shutdownObject



89
90
91
92
# File 'lib/minitest/fork_executor.rb', line 89

def shutdown
  # Nothing to do here but required by Minitest. In a future version, we may
  # reinstate the original Minitest.run_one_method here.
end

#startObject

#start is called by Minitest when initializing the executor. This is where we override some Minitest internals to implement fork-based execution.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/minitest/fork_executor.rb', line 12

def start
  # Store the reference to the original run_one_method method in order to
  # use it to actually run the test case.
  original_run_one_method = Minitest.method(:run_one_method)

  # Remove the original singleton method from Minitest in order to avoid
  # method redefinition warnings when patching it in the next step.
  class << Minitest
    remove_method(:run_one_method)
  end

  # Define a new version of run_one_method that forks, calls the original
  # run_one_method in the child process, and sends results back to the
  # parent. klass and method_name are the two parameters accepted by the
  # original run_one_method - they're the test class (e.g. UserTest) and
  # the test method name (e.g. :test_email_must_be_unique).
  Minitest.define_singleton_method(:run_one_method) do |klass, method_name|
    # Set up a binary pipe for transporting test results from the child
    # to the parent process.
    read_io, write_io = IO.pipe
    read_io.binmode
    write_io.binmode

    if Process.fork
      # The parent process responsible for collecting results.

      # The parent process doesn't write anything.
      write_io.close

      # Load the result object passed by the child process.
      result = Marshal.load(read_io)

      # Unwrap all failures from FailureTransport so that they can be
      # safely presented to the user.
      result.failures.map!(&:failure)

      # We're done reading results from the child so it's safe to close the
      # IO object now.
      read_io.close

      # Wait for the child process to finish before returning the result.
      Process.wait
    else
      # The child process responsible for running the test case.

      # Run the test case method via the original .run_one_method.
      result = original_run_one_method.call(klass, method_name)

      # Wrap failures in FailureTransport to avoid issue when marshalling.
      # Some failures correspond to exceptions referencing unmarshallable
      # objects. For example, a PostgreSQL exception may reference
      # PG::Connection that cannot be marshalled. In those case, we replace
      # the original error with UnmarshallableError retaining as much
      # detail as possible.
      result.failures.map! { |failure| FailureTransport.new(failure) }

      # The child process doesn't read anything.
      read_io.close

      # Dump the result object to the write IO object so that it can be
      # read by the parent process.
      Marshal.dump(result, write_io)

      # We're done sending results to the parent so it's safe to close the
      # IO object now.
      write_io.close

      # Exit the child process as its job is now done.
      exit
    end

    # This value is returned ONLY in the parent process, not in the child
    # process.
    result
  end
end