derailed_benchmarks 1.4.3 → 1.8.1
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 +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
|