derailed_benchmarks 1.4.3 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/check_changelog.yml +11 -8
- data/.travis.yml +9 -7
- data/Appraisals +16 -16
- data/CHANGELOG.md +29 -1
- data/README.md +14 -4
- data/derailed_benchmarks.gemspec +4 -3
- data/gemfiles/rails_5_1.gemfile +3 -1
- data/gemfiles/rails_5_2.gemfile +3 -3
- data/lib/derailed_benchmarks.rb +2 -1
- data/lib/derailed_benchmarks/core_ext/kernel_require.rb +29 -24
- data/lib/derailed_benchmarks/git/commit.rb +36 -0
- data/lib/derailed_benchmarks/git/in_path.rb +59 -0
- data/lib/derailed_benchmarks/git/switch_project.rb +128 -0
- data/lib/derailed_benchmarks/git_switch_project.rb +1 -0
- data/lib/derailed_benchmarks/load_tasks.rb +11 -4
- data/lib/derailed_benchmarks/require_tree.rb +11 -1
- data/lib/derailed_benchmarks/{stats_in_file.rb → stats_for_file.rb} +8 -2
- data/lib/derailed_benchmarks/stats_from_dir.rb +68 -13
- data/lib/derailed_benchmarks/tasks.rb +34 -63
- data/lib/derailed_benchmarks/version.rb +1 -1
- data/test/derailed_benchmarks/core_ext/kernel_require_test.rb +70 -11
- data/test/derailed_benchmarks/git_switch_project_test.rb +83 -0
- data/test/derailed_benchmarks/require_tree_test.rb +1 -1
- data/test/derailed_benchmarks/stats_from_dir_test.rb +57 -9
- data/test/fixtures/require/autoload_child.rb +5 -0
- data/test/fixtures/require/autoload_parent.rb +8 -0
- data/test/fixtures/require/child_one.rb +1 -1
- data/test/fixtures/require/child_two.rb +1 -1
- data/test/fixtures/require/load_child.rb +3 -0
- data/test/fixtures/require/load_parent.rb +5 -0
- data/test/fixtures/require/parent_one.rb +1 -1
- data/test/integration/tasks_test.rb +43 -5
- data/test/rails_app/config/application.rb +2 -0
- data/test/test_helper.rb +6 -1
- metadata +46 -12
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -30,7 +30,7 @@ namespace :perf do
|
|
30
30
|
DERAILED_APP.initialize! unless DERAILED_APP.instance_variable_get(:@initialized)
|
31
31
|
end
|
32
32
|
|
33
|
-
if
|
33
|
+
if !ENV["DERAILED_SKIP_ACTIVE_RECORD"] && defined? ActiveRecord
|
34
34
|
if defined? ActiveRecord::Tasks::DatabaseTasks
|
35
35
|
ActiveRecord::Tasks::DatabaseTasks.create_current
|
36
36
|
else # Rails 3.2
|
@@ -39,7 +39,14 @@ namespace :perf do
|
|
39
39
|
|
40
40
|
ActiveRecord::Migrator.migrations_paths = DERAILED_APP.paths['db/migrate'].to_a
|
41
41
|
ActiveRecord::Migration.verbose = true
|
42
|
-
|
42
|
+
|
43
|
+
if Rails.version.start_with? '6'
|
44
|
+
ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths, ActiveRecord::SchemaMigration).migrate
|
45
|
+
elsif Rails.version.start_with? '5.2'
|
46
|
+
ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths).migrate
|
47
|
+
else
|
48
|
+
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, nil)
|
49
|
+
end
|
43
50
|
end
|
44
51
|
|
45
52
|
DERAILED_APP.config.consider_all_requests_local = true
|
@@ -103,7 +110,7 @@ namespace :perf do
|
|
103
110
|
STDERR.puts "Bad request to #{cmd.inspect} \n\n***RESPONSE***:\n\n#{ response.inspect }"
|
104
111
|
|
105
112
|
FileUtils.mkdir_p("tmp")
|
106
|
-
File.open("tmp/fail.html", "w+") {|f| f.write response
|
113
|
+
File.open("tmp/fail.html", "w+") {|f| f.write response }
|
107
114
|
|
108
115
|
`open #{File.expand_path("tmp/fail.html")}` if ENV["DERAILED_DEBUG"]
|
109
116
|
|
@@ -135,4 +142,4 @@ namespace :perf do
|
|
135
142
|
WARM_COUNT.times { call_app }
|
136
143
|
end
|
137
144
|
end
|
138
|
-
end
|
145
|
+
end
|
@@ -13,6 +13,16 @@ module DerailedBenchmarks
|
|
13
13
|
def initialize(name)
|
14
14
|
@name = name
|
15
15
|
@children = {}
|
16
|
+
@cost = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.reset!
|
20
|
+
REQUIRED_BY.clear
|
21
|
+
if defined?(Kernel::REQUIRE_STACK)
|
22
|
+
Kernel::REQUIRE_STACK.clear
|
23
|
+
|
24
|
+
Kernel::REQUIRE_STACK.push(TOP_REQUIRE)
|
25
|
+
end
|
16
26
|
end
|
17
27
|
|
18
28
|
def <<(tree)
|
@@ -40,7 +50,7 @@ module DerailedBenchmarks
|
|
40
50
|
end
|
41
51
|
|
42
52
|
def to_string
|
43
|
-
str =
|
53
|
+
str = String.new("#{name}: #{cost.round(4)} MiB")
|
44
54
|
if parent && REQUIRED_BY[self.name.to_s]
|
45
55
|
names = REQUIRED_BY[self.name.to_s].uniq - [parent.name.to_s]
|
46
56
|
if names.any?
|
@@ -16,24 +16,30 @@ module DerailedBenchmarks
|
|
16
16
|
# x.average # => 10.5
|
17
17
|
# x.name # => "muhfile"
|
18
18
|
class StatsForFile
|
19
|
-
attr_reader :name, :values, :desc, :time
|
19
|
+
attr_reader :name, :values, :desc, :time, :short_sha
|
20
20
|
|
21
|
-
def initialize(file:, name:, desc: "", time: )
|
21
|
+
def initialize(file:, name:, desc: "", time: , short_sha: nil)
|
22
22
|
@file = Pathname.new(file)
|
23
23
|
FileUtils.touch(@file)
|
24
24
|
|
25
25
|
@name = name
|
26
26
|
@desc = desc
|
27
27
|
@time = time
|
28
|
+
@short_sha = short_sha
|
28
29
|
end
|
29
30
|
|
30
31
|
def call
|
31
32
|
load_file!
|
33
|
+
return if values.empty?
|
32
34
|
|
33
35
|
@median = (values[(values.length - 1) / 2] + values[values.length/ 2]) / 2.0
|
34
36
|
@average = values.inject(:+) / values.length
|
35
37
|
end
|
36
38
|
|
39
|
+
def empty?
|
40
|
+
values.empty?
|
41
|
+
end
|
42
|
+
|
37
43
|
def median
|
38
44
|
@median.to_f
|
39
45
|
end
|
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'bigdecimal'
|
4
4
|
require 'statistics'
|
5
|
+
require 'stringio'
|
6
|
+
require 'mini_histogram'
|
7
|
+
require 'mini_histogram/plot'
|
5
8
|
|
6
9
|
module DerailedBenchmarks
|
7
10
|
# A class used to read several benchmark files
|
@@ -26,14 +29,28 @@ module DerailedBenchmarks
|
|
26
29
|
FORMAT = "%0.4f"
|
27
30
|
attr_reader :stats, :oldest, :newest
|
28
31
|
|
29
|
-
def initialize(
|
32
|
+
def initialize(input)
|
30
33
|
@files = []
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
if input.is_a?(Hash)
|
36
|
+
hash = input
|
37
|
+
hash.each do |branch, info_hash|
|
38
|
+
file = info_hash.fetch(:file)
|
39
|
+
desc = info_hash.fetch(:desc)
|
40
|
+
time = info_hash.fetch(:time)
|
41
|
+
short_sha = info_hash["short_sha"]
|
42
|
+
@files << StatsForFile.new(file: file, desc: desc, time: time, name: branch, short_sha: short_sha)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
input.each do |commit|
|
46
|
+
@files << StatsForFile.new(
|
47
|
+
file: commit.file,
|
48
|
+
desc: commit.desc,
|
49
|
+
time: commit.time,
|
50
|
+
name: commit.ref,
|
51
|
+
short_sha: commit.short_sha
|
52
|
+
)
|
53
|
+
end
|
37
54
|
end
|
38
55
|
@files.sort_by! { |f| f.time }
|
39
56
|
@oldest = @files.first
|
@@ -42,14 +59,27 @@ module DerailedBenchmarks
|
|
42
59
|
|
43
60
|
def call
|
44
61
|
@files.each(&:call)
|
45
|
-
|
62
|
+
|
63
|
+
return self if @files.detect(&:empty?)
|
64
|
+
|
65
|
+
stats_95 = statistical_test(confidence: 95)
|
66
|
+
|
67
|
+
# If default check is good, see if we also pass a more rigorous test
|
68
|
+
# if so, then use the more rigourous test
|
69
|
+
if stats_95[:alternative]
|
70
|
+
stats_99 = statistical_test(confidence: 99)
|
71
|
+
@stats = stats_99 if stats_99[:alternative]
|
72
|
+
end
|
73
|
+
@stats ||= stats_95
|
74
|
+
|
46
75
|
self
|
47
76
|
end
|
48
77
|
|
49
|
-
def statistical_test(series_1=oldest.values, series_2=newest.values)
|
78
|
+
def statistical_test(series_1=oldest.values, series_2=newest.values, confidence: 95)
|
50
79
|
StatisticalTest::KSTest.two_samples(
|
51
80
|
group_one: series_1,
|
52
|
-
group_two: series_2
|
81
|
+
group_two: series_2,
|
82
|
+
alpha: (100 - confidence) / 100.0
|
53
83
|
)
|
54
84
|
end
|
55
85
|
|
@@ -86,10 +116,31 @@ module DerailedBenchmarks
|
|
86
116
|
end
|
87
117
|
|
88
118
|
def align
|
89
|
-
" " * (("
|
119
|
+
" " * (percent_faster.to_s.index(".") - x_faster.to_s.index("."))
|
120
|
+
end
|
121
|
+
|
122
|
+
def histogram(io = $stdout)
|
123
|
+
newest_histogram = MiniHistogram.new(newest.values)
|
124
|
+
oldest_histogram = MiniHistogram.new(oldest.values)
|
125
|
+
MiniHistogram.set_average_edges!(newest_histogram, oldest_histogram)
|
126
|
+
|
127
|
+
{newest => newest_histogram, oldest => oldest_histogram}.each do |report, histogram|
|
128
|
+
plot = histogram.plot(
|
129
|
+
title: "\n#{' ' * 18 }Histogram - [#{report.short_sha || report.name}] #{report.desc.inspect}",
|
130
|
+
ylabel: "Time (s)",
|
131
|
+
xlabel: "# of runs in range"
|
132
|
+
)
|
133
|
+
|
134
|
+
plot.render(io)
|
135
|
+
io.puts
|
136
|
+
end
|
137
|
+
|
138
|
+
io.puts
|
90
139
|
end
|
91
140
|
|
92
|
-
def banner(io =
|
141
|
+
def banner(io = $stdout)
|
142
|
+
return if @files.detect(&:empty?)
|
143
|
+
|
93
144
|
io.puts
|
94
145
|
if significant?
|
95
146
|
io.puts "❤️ ❤️ ❤️ (Statistically Significant) ❤️ ❤️ ❤️"
|
@@ -97,19 +148,23 @@ module DerailedBenchmarks
|
|
97
148
|
io.puts "👎👎👎(NOT Statistically Significant) 👎👎👎"
|
98
149
|
end
|
99
150
|
io.puts
|
100
|
-
io.puts "[#{newest.name}] #{newest.desc.inspect}
|
151
|
+
io.puts "[#{newest.short_sha || newest.name}] (#{FORMAT % newest.median} seconds) #{newest.desc.inspect} ref: #{newest.name.inspect}"
|
101
152
|
io.puts " #{change_direction} by:"
|
102
153
|
io.puts " #{align}#{FORMAT % x_faster}x [older/newer]"
|
103
154
|
io.puts " #{FORMAT % percent_faster}\% [(older - newer) / older * 100]"
|
104
|
-
io.puts "[#{oldest.name}] #{oldest.desc.inspect}
|
155
|
+
io.puts "[#{oldest.short_sha || oldest.name}] (#{FORMAT % oldest.median} seconds) #{oldest.desc.inspect} ref: #{oldest.name.inspect}"
|
105
156
|
io.puts
|
106
157
|
io.puts "Iterations per sample: #{ENV["TEST_COUNT"]}"
|
107
158
|
io.puts "Samples: #{newest.values.length}"
|
108
159
|
io.puts
|
109
160
|
io.puts "Test type: Kolmogorov Smirnov"
|
161
|
+
io.puts "Confidence level: #{@stats[:confidence_level] * 100} %"
|
110
162
|
io.puts "Is significant? (max > critical): #{significant?}"
|
111
163
|
io.puts "D critical: #{d_critical}"
|
112
164
|
io.puts "D max: #{d_max}"
|
165
|
+
|
166
|
+
histogram(io)
|
167
|
+
|
113
168
|
io.puts
|
114
169
|
end
|
115
170
|
end
|
@@ -1,6 +1,12 @@
|
|
1
1
|
require_relative 'load_tasks'
|
2
2
|
|
3
3
|
namespace :perf do
|
4
|
+
desc "runs the performance test against two most recent commits of the current app"
|
5
|
+
task :app do
|
6
|
+
ENV["DERAILED_PATH_TO_LIBRARY"] = '.'
|
7
|
+
Rake::Task["perf:library"].invoke
|
8
|
+
end
|
9
|
+
|
4
10
|
desc "runs the same test against two different branches for statistical comparison"
|
5
11
|
task :library do
|
6
12
|
begin
|
@@ -11,92 +17,57 @@ namespace :perf do
|
|
11
17
|
script = ENV["DERAILED_SCRIPT"] || "bundle exec derailed exec perf:test"
|
12
18
|
|
13
19
|
if ENV["DERAILED_PATH_TO_LIBRARY"]
|
14
|
-
library_dir = ENV["DERAILED_PATH_TO_LIBRARY"]
|
20
|
+
library_dir = ENV["DERAILED_PATH_TO_LIBRARY"].chomp
|
15
21
|
else
|
16
22
|
library_dir = DerailedBenchmarks.rails_path_on_disk
|
17
23
|
end
|
18
|
-
|
19
|
-
raise "Must be a path with a .git directory '#{library_dir}'" unless File.exist?(File.join(library_dir, ".git"))
|
20
|
-
|
21
|
-
# Use either the explicit SHAs when present or grab last two SHAs from commit history
|
22
|
-
# if only one SHA is given, then use it and the last SHA from commit history
|
23
|
-
branch_names = []
|
24
|
-
branch_names = ENV.fetch("SHAS_TO_TEST").split(",") if ENV["SHAS_TO_TEST"]
|
25
|
-
if branch_names.length < 2
|
26
|
-
Dir.chdir(library_dir) do
|
27
|
-
run!("git checkout '#{branch_names.first}'") unless branch_names.empty?
|
28
|
-
|
29
|
-
branches = run!('git log --format="%H" -n 2').chomp.split($/)
|
30
|
-
if branch_names.empty?
|
31
|
-
branch_names = branches
|
32
|
-
else
|
33
|
-
branches.shift
|
34
|
-
branch_names << branches.shift
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
current_library_branch = ""
|
40
|
-
Dir.chdir(library_dir) { current_library_branch = run!('git describe --contains --all HEAD').chomp }
|
24
|
+
library_dir = Pathname.new(library_dir)
|
41
25
|
|
42
26
|
out_dir = Pathname.new("tmp/compare_branches/#{Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N')}")
|
43
27
|
out_dir.mkpath
|
44
28
|
|
45
|
-
|
46
|
-
branch_info = {}
|
47
|
-
branch_to_sha = {}
|
29
|
+
ref_string = ENV["SHAS_TO_TEST"] || ENV["REFS_TO_TEST"] || ""
|
48
30
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
short_sha = run!("git rev-parse --short HEAD").strip
|
55
|
-
branch_to_sha[branch] = short_sha
|
31
|
+
project = DerailedBenchmarks::Git::SwitchProject.new(
|
32
|
+
path: library_dir,
|
33
|
+
ref_array: ref_string.split(","),
|
34
|
+
log_dir: out_dir
|
35
|
+
)
|
56
36
|
|
57
|
-
|
58
|
-
end
|
59
|
-
run!("#{script}")
|
60
|
-
end
|
37
|
+
stats = DerailedBenchmarks::StatsFromDir.new(project.commits)
|
61
38
|
|
39
|
+
# Advertise branch names early to make sure people know what they're testing
|
62
40
|
puts
|
63
41
|
puts
|
64
|
-
|
65
|
-
short_sha
|
66
|
-
desc = branch_info[short_sha][:desc]
|
67
|
-
puts "Testing #{i + 1}: #{short_sha}: #{desc}"
|
42
|
+
project.commits.each_with_index do |commit, i|
|
43
|
+
puts "Testing #{i + 1}: #{commit.short_sha}: #{commit.description}"
|
68
44
|
end
|
69
45
|
puts
|
70
46
|
puts
|
71
47
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
times_significant = 0
|
78
|
-
DERAILED_SCRIPT_COUNT.times do |i|
|
79
|
-
puts "Sample: #{i.next}/#{DERAILED_SCRIPT_COUNT} iterations per sample: #{ENV['TEST_COUNT']}"
|
80
|
-
branches_to_test.each do |branch, file|
|
81
|
-
Dir.chdir(library_dir) { run!("git checkout '#{branch}'") }
|
82
|
-
run!(" #{script} 2>&1 | tail -n 1 >> '#{file}'")
|
83
|
-
end
|
84
|
-
times_significant += 1 if i >= 2 && stats.call.significant?
|
85
|
-
break if stop_valid_count != 0 && times_significant == stop_valid_count
|
86
|
-
end
|
48
|
+
project.restore_branch_on_return do
|
49
|
+
DERAILED_SCRIPT_COUNT.times do |i|
|
50
|
+
puts "Sample: #{i.next}/#{DERAILED_SCRIPT_COUNT} iterations per sample: #{ENV['TEST_COUNT']}"
|
51
|
+
project.commits.each do |commit|
|
52
|
+
commit.checkout!
|
87
53
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
54
|
+
output = run!("#{script} 2>&1")
|
55
|
+
commit.log.open("a") {|f| f.puts output.lines.last }
|
56
|
+
end
|
57
|
+
|
58
|
+
if (i % 50).zero?
|
59
|
+
puts "Intermediate result"
|
60
|
+
stats.call.banner
|
61
|
+
puts "Continuing execution"
|
62
|
+
end
|
93
63
|
end
|
94
64
|
end
|
95
65
|
|
66
|
+
ensure
|
96
67
|
if stats
|
97
68
|
stats.call.banner
|
98
69
|
|
99
|
-
result_file = out_dir
|
70
|
+
result_file = out_dir.join("results.txt")
|
100
71
|
File.open(result_file, "w") do |f|
|
101
72
|
stats.banner(f)
|
102
73
|
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'test_helper'
|
4
4
|
|
5
5
|
class KernelRequireTest < ActiveSupport::TestCase
|
6
|
-
|
7
6
|
setup do
|
8
7
|
require 'derailed_benchmarks/core_ext/kernel_require'
|
9
8
|
GC.disable
|
@@ -11,23 +10,83 @@ class KernelRequireTest < ActiveSupport::TestCase
|
|
11
10
|
|
12
11
|
teardown do
|
13
12
|
GC.enable
|
13
|
+
DerailedBenchmarks::RequireTree.reset!
|
14
|
+
end
|
15
|
+
|
16
|
+
test "profiles load" do
|
17
|
+
in_fork do
|
18
|
+
require fixtures_dir("require/load_parent.rb")
|
19
|
+
|
20
|
+
parent = assert_node_in_parent("load_parent.rb", TOP_REQUIRE)
|
21
|
+
|
22
|
+
assert_node_in_parent("load_child.rb", parent)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
test "profiles autoload" do
|
27
|
+
skip if RUBY_VERSION.start_with?("2.2") # Fails on CI, I can't install Ruby 2.2 locally to debug https://stackoverflow.com/questions/63926460/install-ruby-2-2-on-mac-osx-catalina-with-ruby-install, https://github.com/postmodern/ruby-install/issues/375
|
28
|
+
|
29
|
+
in_fork do
|
30
|
+
require fixtures_dir("require/autoload_parent.rb")
|
31
|
+
parent = assert_node_in_parent("autoload_parent.rb", TOP_REQUIRE)
|
32
|
+
|
33
|
+
assert_node_in_parent("autoload_child.rb", parent)
|
34
|
+
end
|
14
35
|
end
|
15
36
|
|
37
|
+
test "core extension profiles useage" do
|
38
|
+
in_fork do
|
39
|
+
require fixtures_dir("require/parent_one.rb")
|
40
|
+
parent = assert_node_in_parent("parent_one.rb", TOP_REQUIRE)
|
41
|
+
assert_node_in_parent("child_one.rb", parent)
|
42
|
+
child_two = assert_node_in_parent("child_two.rb", parent)
|
43
|
+
assert_node_in_parent("relative_child", parent)
|
44
|
+
assert_node_in_parent("relative_child_two", parent)
|
45
|
+
assert_node_in_parent("raise_child.rb", child_two)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Checks to see that the given file name is present in the
|
50
|
+
# parent tree node and that the memory of that file
|
51
|
+
# is less than the parent (since the parent should include itself
|
52
|
+
# plus its children)
|
53
|
+
#
|
54
|
+
# Returns the child node
|
16
55
|
def assert_node_in_parent(file_name, parent)
|
17
56
|
file = fixtures_dir(File.join("require", file_name))
|
18
57
|
node = parent[file]
|
19
|
-
assert node,
|
20
|
-
|
58
|
+
assert node, "Expected: #{parent.name} to include: #{file.to_s} but it did not.\nChildren: #{parent.children.map(&:name).map(&:to_s)}"
|
59
|
+
unless parent == TOP_REQUIRE
|
60
|
+
assert node.cost < parent.cost, "Expected: #{node.name.inspect} (#{node.cost}) to cost less than: #{parent.name.inspect} (#{parent.cost})"
|
61
|
+
end
|
21
62
|
node
|
22
63
|
end
|
23
64
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
65
|
+
# Used to get semi-clean process memory
|
66
|
+
# It would be better to run the requires in a totally different process
|
67
|
+
# but...that would take engineering
|
68
|
+
#
|
69
|
+
# If I was going to do that, I would find a way to serialize RequireTree
|
70
|
+
# into a json structure with file names and costs, run the script
|
71
|
+
# dump the json to a file, then in this process read the file and
|
72
|
+
# run assertions
|
73
|
+
def in_fork
|
74
|
+
Tempfile.create("stdout") do |tmp_file|
|
75
|
+
pid = fork do
|
76
|
+
$stdout.reopen(tmp_file, "w")
|
77
|
+
$stderr.reopen(tmp_file, "w")
|
78
|
+
$stdout.sync = true
|
79
|
+
$stderr.sync = true
|
80
|
+
yield
|
81
|
+
Kernel.exit!(0) # needed for https://github.com/seattlerb/minitest/pull/683
|
82
|
+
end
|
83
|
+
Process.waitpid(pid)
|
84
|
+
|
85
|
+
if $?.success?
|
86
|
+
print File.read(tmp_file)
|
87
|
+
else
|
88
|
+
raise File.read(tmp_file)
|
89
|
+
end
|
90
|
+
end
|
32
91
|
end
|
33
92
|
end
|