simplecov 0.10.0 → 0.11.0
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 +7 -7
- data/.rspec +3 -1
- data/.rubocop.yml +6 -5
- data/CHANGELOG.md +16 -2
- data/Gemfile +14 -14
- data/README.md +28 -12
- data/Rakefile +5 -8
- data/features/config_formatters.feature +1 -1
- data/features/config_tracked_files.feature +29 -0
- data/features/step_definitions/transformers.rb +1 -1
- data/features/support/env.rb +6 -3
- data/lib/simplecov.rb +19 -2
- data/lib/simplecov/configuration.rb +24 -4
- data/lib/simplecov/defaults.rb +28 -5
- data/lib/simplecov/file_list.rb +11 -0
- data/lib/simplecov/filter.rb +1 -1
- data/lib/simplecov/formatter/multi_formatter.rb +17 -13
- data/lib/simplecov/result.rb +1 -1
- data/lib/simplecov/source_file.rb +6 -3
- data/lib/simplecov/version.rb +1 -1
- data/spec/1_8_fallbacks_spec.rb +29 -0
- data/spec/command_guesser_spec.rb +46 -0
- data/spec/deleted_source_spec.rb +12 -0
- data/{test → spec}/faked_project/Gemfile +0 -0
- data/{test → spec}/faked_project/Rakefile +0 -0
- data/{test → spec}/faked_project/cucumber.yml +0 -0
- data/{test → spec}/faked_project/features/step_definitions/my_steps.rb +0 -0
- data/{test → spec}/faked_project/features/support/env.rb +0 -0
- data/{test → spec}/faked_project/features/test_stuff.feature +0 -0
- data/{test → spec}/faked_project/lib/faked_project.rb +1 -1
- data/{test → spec}/faked_project/lib/faked_project/framework_specific.rb +0 -0
- data/{test → spec}/faked_project/lib/faked_project/meta_magic.rb +0 -0
- data/{test → spec}/faked_project/lib/faked_project/some_class.rb +0 -0
- data/spec/faked_project/lib/faked_project/untested_class.rb +11 -0
- data/{test → spec}/faked_project/spec/faked_spec.rb +0 -0
- data/{test → spec}/faked_project/spec/forking_spec.rb +0 -0
- data/{test → spec}/faked_project/spec/meta_magic_spec.rb +0 -0
- data/{test → spec}/faked_project/spec/some_class_spec.rb +0 -0
- data/{test → spec}/faked_project/spec/spec_helper.rb +0 -0
- data/{test → spec}/faked_project/test/faked_test.rb +0 -0
- data/{test → spec}/faked_project/test/meta_magic_test.rb +0 -0
- data/{test → spec}/faked_project/test/some_class_test.rb +0 -0
- data/{test → spec}/faked_project/test/test_helper.rb +0 -0
- data/spec/file_list_spec.rb +48 -0
- data/spec/filters_spec.rb +96 -0
- data/{test → spec}/fixtures/app/controllers/sample_controller.rb +0 -0
- data/{test → spec}/fixtures/app/models/user.rb +0 -0
- data/{test → spec}/fixtures/deleted_source_sample.rb +0 -0
- data/{test → spec}/fixtures/frameworks/rspec_bad.rb +0 -0
- data/{test → spec}/fixtures/frameworks/rspec_good.rb +0 -0
- data/{test → spec}/fixtures/frameworks/testunit_bad.rb +0 -0
- data/{test → spec}/fixtures/frameworks/testunit_good.rb +0 -0
- data/{test → spec}/fixtures/iso-8859.rb +0 -0
- data/{test → spec}/fixtures/resultset1.rb +0 -0
- data/{test → spec}/fixtures/resultset2.rb +0 -0
- data/{test → spec}/fixtures/sample.rb +0 -0
- data/{test → spec}/fixtures/utf-8.rb +0 -0
- data/{test → spec}/helper.rb +3 -7
- data/spec/merge_helpers_spec.rb +108 -0
- data/spec/result_spec.rb +207 -0
- data/spec/return_codes_spec.rb +37 -0
- data/spec/source_file_line_spec.rb +153 -0
- data/spec/source_file_spec.rb +75 -0
- metadata +163 -149
- data/test/shoulda_macros.rb +0 -19
- data/test/test_1_8_fallbacks.rb +0 -31
- data/test/test_command_guesser.rb +0 -19
- data/test/test_deleted_source.rb +0 -14
- data/test/test_file_list.rb +0 -23
- data/test/test_filters.rb +0 -94
- data/test/test_merge_helpers.rb +0 -112
- data/test/test_result.rb +0 -175
- data/test/test_return_codes.rb +0 -41
- data/test/test_source_file.rb +0 -73
- data/test/test_source_file_line.rb +0 -106
data/lib/simplecov/file_list.rb
CHANGED
@@ -26,6 +26,17 @@ module SimpleCov
|
|
26
26
|
map { |f| f.skipped_lines.count }.inject(&:+)
|
27
27
|
end
|
28
28
|
|
29
|
+
# Computes the coverage based upon lines covered and lines missed for each file
|
30
|
+
# Returns an array with all coverage percentages
|
31
|
+
def covered_percentages
|
32
|
+
map(&:covered_percent)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Finds the least covered file and returns that file's name
|
36
|
+
def least_covered_file
|
37
|
+
sort_by(&:covered_percent).first.filename
|
38
|
+
end
|
39
|
+
|
29
40
|
# Returns the overall amount of relevant lines of code across all files in this list
|
30
41
|
def lines_of_code
|
31
42
|
covered_lines + missed_lines
|
data/lib/simplecov/filter.rb
CHANGED
@@ -21,7 +21,7 @@ module SimpleCov
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def passes?(source_file)
|
24
|
-
warn "
|
24
|
+
warn "#{Kernel.caller.first}: [DEPRECATION] #passes? is deprecated. Use #matches? instead."
|
25
25
|
matches?(source_file)
|
26
26
|
end
|
27
27
|
end
|
@@ -1,27 +1,31 @@
|
|
1
1
|
module SimpleCov
|
2
2
|
module Formatter
|
3
3
|
class MultiFormatter
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module InstanceMethods
|
5
|
+
def format(result)
|
6
|
+
formatters.map do |formatter|
|
7
|
+
begin
|
8
|
+
formatter.new.format(result)
|
9
|
+
rescue => e
|
10
|
+
STDERR.puts("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
|
11
|
+
nil
|
12
|
+
end
|
8
13
|
end
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
rescue => e
|
17
|
-
STDERR.puts("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
|
18
|
-
nil
|
17
|
+
def self.new(formatters = nil)
|
18
|
+
Class.new do
|
19
|
+
define_method :formatters do
|
20
|
+
@formatters ||= Array(formatters)
|
19
21
|
end
|
22
|
+
include InstanceMethods
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
26
|
+
def self.[](*args)
|
27
|
+
warn "#{Kernel.caller.first}: [DEPRECATION] ::[] is deprecated. Use ::new instead."
|
28
|
+
new(*args)
|
25
29
|
end
|
26
30
|
end
|
27
31
|
end
|
data/lib/simplecov/result.rb
CHANGED
@@ -18,7 +18,7 @@ module SimpleCov
|
|
18
18
|
# Explicitly set the command name that was used for this coverage result. Defaults to SimpleCov.command_name
|
19
19
|
attr_writer :command_name
|
20
20
|
|
21
|
-
def_delegators :files, :covered_percent, :covered_strength, :covered_lines, :missed_lines
|
21
|
+
def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines
|
22
22
|
def_delegator :files, :lines_of_code, :total_lines
|
23
23
|
|
24
24
|
# Initialize a new SimpleCov::Result from given Coverage.result (a Hash of filenames each containing an array of
|
@@ -28,8 +28,10 @@ module SimpleCov
|
|
28
28
|
fail ArgumentError, "Only String accepted for source" unless src.is_a?(String)
|
29
29
|
fail ArgumentError, "Only Fixnum accepted for line_number" unless line_number.is_a?(Fixnum)
|
30
30
|
fail ArgumentError, "Only Fixnum and nil accepted for coverage" unless coverage.is_a?(Fixnum) || coverage.nil?
|
31
|
-
@src
|
32
|
-
@
|
31
|
+
@src = src
|
32
|
+
@line_number = line_number
|
33
|
+
@coverage = coverage
|
34
|
+
@skipped = false
|
33
35
|
end
|
34
36
|
|
35
37
|
# Returns true if this is a line that should have been covered, but was not
|
@@ -77,7 +79,8 @@ module SimpleCov
|
|
77
79
|
alias_method :source, :src
|
78
80
|
|
79
81
|
def initialize(filename, coverage)
|
80
|
-
@filename
|
82
|
+
@filename = filename
|
83
|
+
@coverage = coverage
|
81
84
|
File.open(filename, "rb") { |f| @src = f.readlines }
|
82
85
|
end
|
83
86
|
|
data/lib/simplecov/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
# Tests that verify that on 1.8 versions of ruby, simplecov simply
|
4
|
+
# does not launch and does not cause errors on the way
|
5
|
+
#
|
6
|
+
# TODO: This should be expanded upon all methods that could potentially
|
7
|
+
# be called in a test/spec-helper simplecov config block
|
8
|
+
#
|
9
|
+
describe "Ruby 1.8 fallback" do
|
10
|
+
it "return false when calling SimpleCov.start" do
|
11
|
+
expect(SimpleCov.start).to be false
|
12
|
+
end
|
13
|
+
|
14
|
+
it "return false when calling SimpleCov.start with a block" do
|
15
|
+
expect(SimpleCov.start { fail "Shouldn't reach this!" }).to be false
|
16
|
+
end
|
17
|
+
|
18
|
+
it "return false when calling SimpleCov.configure with a block" do
|
19
|
+
expect(SimpleCov.configure { fail "Shouldn't reach this!" }).to be false
|
20
|
+
end
|
21
|
+
|
22
|
+
it "allow to define a profile" do
|
23
|
+
expect do
|
24
|
+
SimpleCov.profiles.define "testprofile" do
|
25
|
+
add_filter "/config/"
|
26
|
+
end
|
27
|
+
end.not_to raise_error
|
28
|
+
end
|
29
|
+
end if RUBY_VERSION.start_with? "1.8"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe SimpleCov::CommandGuesser do
|
4
|
+
subject { SimpleCov::CommandGuesser }
|
5
|
+
it 'correctly guesses "Unit Tests" for unit tests' do
|
6
|
+
subject.original_run_command = "/some/path/test/units/foo_bar_test.rb"
|
7
|
+
expect(subject.guess).to eq("Unit Tests")
|
8
|
+
subject.original_run_command = "test/units/foo.rb"
|
9
|
+
expect(subject.guess).to eq("Unit Tests")
|
10
|
+
subject.original_run_command = "test/foo.rb"
|
11
|
+
expect(subject.guess).to eq("Unit Tests")
|
12
|
+
subject.original_run_command = "test/{models,helpers,unit}/**/*_test.rb"
|
13
|
+
expect(subject.guess).to eq("Unit Tests")
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'correctly guesses "Functional Tests" for functional tests' do
|
17
|
+
subject.original_run_command = "/some/path/test/functional/foo_bar_controller_test.rb"
|
18
|
+
expect(subject.guess).to eq("Functional Tests")
|
19
|
+
subject.original_run_command = "test/{controllers,mailers,functional}/**/*_test.rb"
|
20
|
+
expect(subject.guess).to eq("Functional Tests")
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'correctly guesses "Integration Tests" for integration tests' do
|
24
|
+
subject.original_run_command = "/some/path/test/integration/foo_bar_controller_test.rb"
|
25
|
+
expect(subject.guess).to eq("Integration Tests")
|
26
|
+
subject.original_run_command = "test/integration/**/*_test.rb"
|
27
|
+
expect(subject.guess).to eq("Integration Tests")
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'correctly guesses "Cucumber Features" for cucumber features' do
|
31
|
+
subject.original_run_command = "features"
|
32
|
+
expect(subject.guess).to eq("Cucumber Features")
|
33
|
+
subject.original_run_command = "cucumber"
|
34
|
+
expect(subject.guess).to eq("Cucumber Features")
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'correctly guesses "RSpec" for RSpec' do
|
38
|
+
subject.original_run_command = "/some/path/spec/foo.rb"
|
39
|
+
expect(subject.guess).to eq("RSpec")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "defaults to RSpec because RSpec constant is defined" do
|
43
|
+
subject.original_run_command = "some_arbitrary_command with arguments"
|
44
|
+
expect(subject.guess).to eq("RSpec")
|
45
|
+
end
|
46
|
+
end if SimpleCov.usable?
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
# Test to verify correct handling of deleted files
|
4
|
+
# See https://github.com/colszowka/simplecov/issues/9
|
5
|
+
describe "A source file which is subsequently deleted" do
|
6
|
+
it "does not cause an error" do
|
7
|
+
Dir.chdir(File.join(File.dirname(__FILE__), "fixtures")) do
|
8
|
+
`ruby deleted_source_sample.rb`
|
9
|
+
expect($?.exitstatus).to be_zero
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -4,7 +4,7 @@ class FakedProject
|
|
4
4
|
end
|
5
5
|
end
|
6
6
|
|
7
|
-
Dir[File.join(File.dirname(__FILE__), "faked_project/*.rb")].each do |file|
|
7
|
+
Dir[File.join(File.dirname(__FILE__), "faked_project/*.rb")].reject { |f| /untested/.match(f) }.each do |file|
|
8
8
|
require file # Require all source files in project dynamically so we can inject some stuff depending on test situation
|
9
9
|
end
|
10
10
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe SimpleCov::Result do
|
4
|
+
subject do
|
5
|
+
original_result = {
|
6
|
+
source_fixture("sample.rb") => [nil, 1, 1, 1, nil, nil, 1, 1, nil, nil],
|
7
|
+
source_fixture("app/models/user.rb") => [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil],
|
8
|
+
source_fixture("app/controllers/sample_controller.rb") => [nil, 2, 2, 0, nil, nil, 0, nil, nil, nil],
|
9
|
+
}
|
10
|
+
SimpleCov::Result.new(original_result).files
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has 11 covered lines" do
|
14
|
+
expect(subject.covered_lines).to eq(11)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has 3 missed lines" do
|
18
|
+
expect(subject.missed_lines).to eq(3)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has 19 never lines" do
|
22
|
+
expect(subject.never_lines).to eq(19)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "has 14 lines of code" do
|
26
|
+
expect(subject.lines_of_code).to eq(14)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "has 3 skipped lines" do
|
30
|
+
expect(subject.skipped_lines).to eq(3)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "has the correct covered percent" do
|
34
|
+
expect(subject.covered_percent).to eq(78.57142857142857)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has the correct covered percentages" do
|
38
|
+
expect(subject.covered_percentages).to eq([50.0, 80.0, 100.0])
|
39
|
+
end
|
40
|
+
|
41
|
+
it "has the correct least covered file" do
|
42
|
+
expect(subject.least_covered_file).to match(/sample_controller.rb/)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "has the correct covered strength" do
|
46
|
+
expect(subject.covered_strength).to eq(0.9285714285714286)
|
47
|
+
end
|
48
|
+
end if SimpleCov.usable?
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe SimpleCov::SourceFile do
|
4
|
+
subject do
|
5
|
+
SimpleCov::SourceFile.new(source_fixture("sample.rb"), [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil])
|
6
|
+
end
|
7
|
+
|
8
|
+
it "doesn't match a new SimpleCov::StringFilter 'foobar'" do
|
9
|
+
expect(SimpleCov::StringFilter.new("foobar")).not_to be_matches subject
|
10
|
+
end
|
11
|
+
|
12
|
+
it "doesn't match a new SimpleCov::StringFilter 'some/path'" do
|
13
|
+
expect(SimpleCov::StringFilter.new("some/path")).not_to be_matches subject
|
14
|
+
end
|
15
|
+
|
16
|
+
it "matches a new SimpleCov::StringFilter 'spec/fixtures'" do
|
17
|
+
expect(SimpleCov::StringFilter.new("spec/fixtures")).to be_matches subject
|
18
|
+
end
|
19
|
+
|
20
|
+
it "matches a new SimpleCov::StringFilter 'spec/fixtures/sample.rb'" do
|
21
|
+
expect(SimpleCov::StringFilter.new("spec/fixtures/sample.rb")).to be_matches subject
|
22
|
+
end
|
23
|
+
|
24
|
+
it "matches a new SimpleCov::StringFilter 'sample.rb'" do
|
25
|
+
expect(SimpleCov::StringFilter.new("sample.rb")).to be_matches subject
|
26
|
+
end
|
27
|
+
|
28
|
+
it "doesn't match a new SimpleCov::BlockFilter that is not applicable" do
|
29
|
+
expect(SimpleCov::BlockFilter.new(proc { |s| File.basename(s.filename) == "foo.rb" })).not_to be_matches subject
|
30
|
+
end
|
31
|
+
|
32
|
+
it "matches a new SimpleCov::BlockFilter that is applicable" do
|
33
|
+
expect(SimpleCov::BlockFilter.new(proc { |s| File.basename(s.filename) == "sample.rb" })).to be_matches subject
|
34
|
+
end
|
35
|
+
|
36
|
+
it "matches a new SimpleCov::ArrayFilter when 'sample.rb' is passed as array" do
|
37
|
+
expect(SimpleCov::ArrayFilter.new(["sample.rb"])).to be_matches subject
|
38
|
+
end
|
39
|
+
|
40
|
+
it "doesn't match a new SimpleCov::ArrayFilter when a file path different than 'sample.rb' is passed as array" do
|
41
|
+
expect(SimpleCov::ArrayFilter.new(["other_file.rb"])).not_to be_matches subject
|
42
|
+
end
|
43
|
+
|
44
|
+
it "matches a new SimpleCov::ArrayFilter when two file paths including 'sample.rb' are passed as array" do
|
45
|
+
expect(SimpleCov::ArrayFilter.new(["sample.rb", "other_file.rb"])).to be_matches subject
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with no filters set up and a basic source file in an array" do
|
49
|
+
before do
|
50
|
+
@prev_filters = SimpleCov.filters
|
51
|
+
SimpleCov.filters = []
|
52
|
+
end
|
53
|
+
|
54
|
+
subject do
|
55
|
+
[SimpleCov::SourceFile.new(source_fixture("sample.rb"), [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil])]
|
56
|
+
end
|
57
|
+
|
58
|
+
after do
|
59
|
+
SimpleCov.filters = @prev_filters
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns 0 items after executing SimpleCov.filtered on files when using a "sample" string filter' do
|
63
|
+
SimpleCov.add_filter "sample"
|
64
|
+
expect(SimpleCov.filtered(subject).count).to be_zero
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns 0 items after executing SimpleCov.filtered on files when using a "spec/fixtures" string filter' do
|
68
|
+
SimpleCov.add_filter "spec/fixtures"
|
69
|
+
expect(SimpleCov.filtered(subject).count).to be_zero
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns 1 item after executing SimpleCov.filtered on files when using a "fooo" string filter' do
|
73
|
+
SimpleCov.add_filter "fooo"
|
74
|
+
expect(SimpleCov.filtered(subject).count).to eq(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "returns 0 items after executing SimpleCov.filtered on files when using a block filter that returns true" do
|
78
|
+
SimpleCov.add_filter do
|
79
|
+
true
|
80
|
+
end
|
81
|
+
expect(SimpleCov.filtered(subject).count).to be_zero
|
82
|
+
end
|
83
|
+
|
84
|
+
it "returns 1 item after executing SimpleCov.filtered on files when using an always-false block filter" do
|
85
|
+
SimpleCov.add_filter do
|
86
|
+
false
|
87
|
+
end
|
88
|
+
expect(SimpleCov.filtered(subject).count).to eq(1)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns a FileList after filtering" do
|
92
|
+
SimpleCov.add_filter "fooo"
|
93
|
+
expect(SimpleCov.filtered(subject)).to be_a SimpleCov::FileList
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end if SimpleCov.usable?
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/{test → spec}/helper.rb
RENAMED
@@ -1,7 +1,5 @@
|
|
1
|
-
require "bundler/setup"
|
2
1
|
require "simplecov"
|
3
|
-
require "
|
4
|
-
require "shoulda"
|
2
|
+
require "rspec"
|
5
3
|
|
6
4
|
SimpleCov.coverage_dir("tmp/coverage")
|
7
5
|
|
@@ -9,9 +7,6 @@ def source_fixture(filename)
|
|
9
7
|
File.expand_path(File.join(File.dirname(__FILE__), "fixtures", filename))
|
10
8
|
end
|
11
9
|
|
12
|
-
require "shoulda_macros"
|
13
|
-
Minitest::Test.send :extend, ShouldaMacros
|
14
|
-
|
15
10
|
# Taken from http://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby
|
16
11
|
require "stringio"
|
17
12
|
|
@@ -19,7 +14,8 @@ def capture_stderr
|
|
19
14
|
# The output stream must be an IO-like object. In this case we capture it in
|
20
15
|
# an in-memory IO object so we can return the string value. You can assign any
|
21
16
|
# IO object here.
|
22
|
-
previous_stderr
|
17
|
+
previous_stderr = $stderr
|
18
|
+
$stderr = StringIO.new
|
23
19
|
yield
|
24
20
|
$stderr.string
|
25
21
|
ensure
|