parallelized_specs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -0
- data/Gemfile.lock +30 -0
- data/Rakefile +21 -0
- data/Readme.md +240 -0
- data/VERSION +1 -0
- data/bin/parallel_spec +2 -0
- data/bin/parallel_test +97 -0
- data/lib/parallel_specs/spec_error_count_logger.rb +30 -0
- data/lib/parallel_specs/spec_error_logger.rb +45 -0
- data/lib/parallel_specs/spec_failures_logger.rb +43 -0
- data/lib/parallel_specs/spec_logger_base.rb +56 -0
- data/lib/parallel_specs/spec_runtime_logger.rb +34 -0
- data/lib/parallel_specs/spec_start_finish_logger.rb +38 -0
- data/lib/parallel_specs/spec_summary_logger.rb +19 -0
- data/lib/parallel_specs.rb +52 -0
- data/lib/parallel_tests/grouper.rb +49 -0
- data/lib/parallel_tests/railtie.rb +10 -0
- data/lib/parallel_tests/runtime_logger.rb +78 -0
- data/lib/parallel_tests/tasks.rb +80 -0
- data/lib/parallel_tests.rb +163 -0
- data/lib/tasks/parallel_tests.rake +1 -0
- data/parallelized_specs.gemspec +64 -0
- data/spec/integration_spec.rb +133 -0
- data/spec/parallel_specs/spec_failure_logger_spec.rb +82 -0
- data/spec/parallel_specs/spec_runtime_logger_spec.rb +76 -0
- data/spec/parallel_specs/spec_summary_logger_spec.rb +33 -0
- data/spec/parallel_specs_spec.rb +165 -0
- data/spec/parallel_tests/runtime_logger_spec.rb +74 -0
- data/spec/parallel_tests_spec.rb +229 -0
- data/spec/spec_helper.rb +149 -0
- metadata +109 -0
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
require 'parallel_tests/grouper'
|
3
|
+
require 'parallel_tests/railtie'
|
4
|
+
|
5
|
+
class ParallelTests
|
6
|
+
VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
|
7
|
+
|
8
|
+
# parallel:spec[:count, :pattern, :options]
|
9
|
+
def self.parse_rake_args(args)
|
10
|
+
# order as given by user
|
11
|
+
args = [args[:count], args[:pattern], args[:options]]
|
12
|
+
|
13
|
+
# count given or empty ?
|
14
|
+
# parallel:spec[2,models,options]
|
15
|
+
# parallel:spec[,models,options]
|
16
|
+
count = args.shift if args.first.to_s =~ /^\d*$/
|
17
|
+
num_processes = count.to_i unless count.to_s.empty?
|
18
|
+
num_processes ||= ENV['PARALLEL_TEST_PROCESSORS'].to_i if ENV['PARALLEL_TEST_PROCESSORS']
|
19
|
+
num_processes ||= Parallel.processor_count
|
20
|
+
|
21
|
+
pattern = args.shift
|
22
|
+
options = args.shift
|
23
|
+
|
24
|
+
[num_processes.to_i, pattern.to_s, options.to_s]
|
25
|
+
end
|
26
|
+
|
27
|
+
# finds all tests and partitions them into groups
|
28
|
+
def self.tests_in_groups(root, num_groups, options={})
|
29
|
+
tests = find_tests(root, options)
|
30
|
+
if options[:no_sort] == true
|
31
|
+
Grouper.in_groups(tests, num_groups)
|
32
|
+
else
|
33
|
+
tests = with_runtime_info(tests)
|
34
|
+
Grouper.in_even_groups_by_size(tests, num_groups, options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.run_tests(test_files, process_number, options)
|
39
|
+
require_list = test_files.map { |filename| %{"#{File.expand_path filename}"} }.join(",")
|
40
|
+
cmd = "ruby -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"
|
41
|
+
execute_command(cmd, process_number, options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.execute_command(cmd, process_number, options)
|
45
|
+
cmd = "TEST_ENV_NUMBER=#{test_env_number(process_number)} ; export TEST_ENV_NUMBER; #{cmd}"
|
46
|
+
f = open("|#{cmd}", 'r')
|
47
|
+
output = fetch_output(f, options)
|
48
|
+
f.close
|
49
|
+
{:stdout => output, :exit_status => $?.exitstatus}
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.find_results(test_output)
|
53
|
+
test_output.split("\n").map {|line|
|
54
|
+
line = line.gsub(/\.|F|\*/,'')
|
55
|
+
next unless line_is_result?(line)
|
56
|
+
line
|
57
|
+
}.compact
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.test_env_number(process_number)
|
61
|
+
process_number == 0 ? '' : process_number + 1
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.runtime_log
|
65
|
+
'tmp/parallel_runtime_test.log'
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.summarize_results(results)
|
69
|
+
results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
|
70
|
+
counts = results.scan(/(\d+) (\w+)/)
|
71
|
+
sums = counts.inject(Hash.new(0)) do |sum, (number, word)|
|
72
|
+
sum[word] += number.to_i
|
73
|
+
sum
|
74
|
+
end
|
75
|
+
sums.sort.map{|word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
# read output of the process and print in in chucks
|
81
|
+
def self.fetch_output(process, options)
|
82
|
+
all = ''
|
83
|
+
buffer = ''
|
84
|
+
timeout = options[:chunk_timeout] || 0.2
|
85
|
+
flushed = Time.now.to_f
|
86
|
+
|
87
|
+
while char = process.getc
|
88
|
+
char = (char.is_a?(Fixnum) ? char.chr : char) # 1.8 <-> 1.9
|
89
|
+
all << char
|
90
|
+
|
91
|
+
# print in chunks so large blocks stay together
|
92
|
+
now = Time.now.to_f
|
93
|
+
buffer << char
|
94
|
+
if flushed + timeout < now
|
95
|
+
print buffer
|
96
|
+
STDOUT.flush
|
97
|
+
buffer = ''
|
98
|
+
flushed = now
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# print the remainder
|
103
|
+
print buffer
|
104
|
+
STDOUT.flush
|
105
|
+
|
106
|
+
all
|
107
|
+
end
|
108
|
+
|
109
|
+
# copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
|
110
|
+
def self.bundler_enabled?
|
111
|
+
return true if Object.const_defined?(:Bundler)
|
112
|
+
|
113
|
+
previous = nil
|
114
|
+
current = File.expand_path(Dir.pwd)
|
115
|
+
|
116
|
+
until !File.directory?(current) || current == previous
|
117
|
+
filename = File.join(current, "Gemfile")
|
118
|
+
return true if File.exists?(filename)
|
119
|
+
current, previous = File.expand_path("..", current), current
|
120
|
+
end
|
121
|
+
|
122
|
+
false
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.line_is_result?(line)
|
126
|
+
line =~ /\d+ failure/
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.test_suffix
|
130
|
+
"_test.rb"
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.with_runtime_info(tests)
|
134
|
+
lines = File.read(runtime_log).split("\n") rescue []
|
135
|
+
|
136
|
+
# use recorded test runtime if we got enough data
|
137
|
+
if lines.size * 1.5 > tests.size
|
138
|
+
puts "Using recorded test runtime"
|
139
|
+
times = Hash.new(1)
|
140
|
+
lines.each do |line|
|
141
|
+
test, time = line.split(":")
|
142
|
+
next unless test and time
|
143
|
+
times[File.expand_path(test)] = time.to_f
|
144
|
+
end
|
145
|
+
tests.sort.map{|test| [test, times[test]] }
|
146
|
+
else # use file sizes
|
147
|
+
tests.sort.map{|test| [test, File.stat(test).size] }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.find_tests(root, options={})
|
152
|
+
if root.is_a?(Array)
|
153
|
+
root
|
154
|
+
else
|
155
|
+
# follow one symlink and direct children
|
156
|
+
# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
|
157
|
+
files = Dir["#{root}/**{,/*/**}/*#{test_suffix}"].uniq
|
158
|
+
files = files.map{|f| f.sub(root+'/','') }
|
159
|
+
files = files.grep(/#{options[:pattern]}/)
|
160
|
+
files.map{|f| "#{root}/#{f}" }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../parallel_tests/tasks")
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "parallelized_specs"
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jake Sorce, Bryan Madsen"]
|
12
|
+
s.date = "2012-04-17"
|
13
|
+
s.email = "jake@instructure.com"
|
14
|
+
s.executables = ["parallel_test", "parallel_spec"]
|
15
|
+
s.files = [
|
16
|
+
"Gemfile",
|
17
|
+
"Gemfile.lock",
|
18
|
+
"Rakefile",
|
19
|
+
"Readme.md",
|
20
|
+
"VERSION",
|
21
|
+
"bin/parallel_spec",
|
22
|
+
"bin/parallel_test",
|
23
|
+
"lib/parallel_specs.rb",
|
24
|
+
"lib/parallel_specs/spec_error_count_logger.rb",
|
25
|
+
"lib/parallel_specs/spec_error_logger.rb",
|
26
|
+
"lib/parallel_specs/spec_failures_logger.rb",
|
27
|
+
"lib/parallel_specs/spec_logger_base.rb",
|
28
|
+
"lib/parallel_specs/spec_runtime_logger.rb",
|
29
|
+
"lib/parallel_specs/spec_start_finish_logger.rb",
|
30
|
+
"lib/parallel_specs/spec_summary_logger.rb",
|
31
|
+
"lib/parallel_tests.rb",
|
32
|
+
"lib/parallel_tests/grouper.rb",
|
33
|
+
"lib/parallel_tests/railtie.rb",
|
34
|
+
"lib/parallel_tests/runtime_logger.rb",
|
35
|
+
"lib/parallel_tests/tasks.rb",
|
36
|
+
"lib/tasks/parallel_tests.rake",
|
37
|
+
"parallelized_specs.gemspec",
|
38
|
+
"spec/integration_spec.rb",
|
39
|
+
"spec/parallel_specs/spec_failure_logger_spec.rb",
|
40
|
+
"spec/parallel_specs/spec_runtime_logger_spec.rb",
|
41
|
+
"spec/parallel_specs/spec_summary_logger_spec.rb",
|
42
|
+
"spec/parallel_specs_spec.rb",
|
43
|
+
"spec/parallel_tests/runtime_logger_spec.rb",
|
44
|
+
"spec/parallel_tests_spec.rb",
|
45
|
+
"spec/spec_helper.rb"
|
46
|
+
]
|
47
|
+
s.homepage = "http://github.com/jake/parallelized_specs"
|
48
|
+
s.require_paths = ["lib"]
|
49
|
+
s.rubygems_version = "1.8.22"
|
50
|
+
s.summary = "Run rspec tests in parallel"
|
51
|
+
|
52
|
+
if s.respond_to? :specification_version then
|
53
|
+
s.specification_version = 3
|
54
|
+
|
55
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
56
|
+
s.add_runtime_dependency(%q<parallel>, [">= 0"])
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<parallel>, [">= 0"])
|
59
|
+
end
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<parallel>, [">= 0"])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'CLI' do
|
4
|
+
before do
|
5
|
+
`rm -rf #{folder}`
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
`rm -rf #{folder}`
|
10
|
+
end
|
11
|
+
|
12
|
+
def folder
|
13
|
+
"/tmp/parallel_tests_tests"
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(file, content)
|
17
|
+
path = "#{folder}/#{file}"
|
18
|
+
`mkdir -p #{File.dirname(path)}` unless File.exist?(File.dirname(path))
|
19
|
+
File.open(path, 'w'){|f| f.write content }
|
20
|
+
path
|
21
|
+
end
|
22
|
+
|
23
|
+
def bin_folder
|
24
|
+
"#{File.expand_path(File.dirname(__FILE__))}/../bin"
|
25
|
+
end
|
26
|
+
|
27
|
+
def executable
|
28
|
+
"#{bin_folder}/parallel_test"
|
29
|
+
end
|
30
|
+
|
31
|
+
def run_tests(options={})
|
32
|
+
`cd #{folder} && #{executable} --chunk-timeout 999 -t #{options[:type] || 'spec'} -n #{options[:processes]||2} #{options[:add]} 2>&1`
|
33
|
+
end
|
34
|
+
|
35
|
+
it "runs tests in parallel" do
|
36
|
+
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
|
37
|
+
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
|
38
|
+
result = run_tests
|
39
|
+
|
40
|
+
# test ran and gave their puts
|
41
|
+
result.should include('TEST1')
|
42
|
+
result.should include('TEST2')
|
43
|
+
|
44
|
+
# all results present
|
45
|
+
result.scan('1 example, 0 failure').size.should == 2 # 2 results
|
46
|
+
result.scan('2 examples, 0 failures').size.should == 1 # 1 summary
|
47
|
+
result.scan(/Finished in \d+\.\d+ seconds/).size.should == 2
|
48
|
+
result.scan(/Took \d+\.\d+ seconds/).size.should == 1 # parallel summary
|
49
|
+
$?.success?.should == true
|
50
|
+
end
|
51
|
+
|
52
|
+
it "does not run any tests if there are none" do
|
53
|
+
write 'spec/xxx.rb', 'xxx'
|
54
|
+
result = run_tests
|
55
|
+
result.should include('No examples found')
|
56
|
+
result.should include('Took')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "fails when tests fail" do
|
60
|
+
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
|
61
|
+
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){1.should == 2}}'
|
62
|
+
result = run_tests
|
63
|
+
|
64
|
+
result.scan('1 example, 1 failure').size.should == 1
|
65
|
+
result.scan('1 example, 0 failure').size.should == 1
|
66
|
+
result.scan('2 examples, 1 failure').size.should == 1
|
67
|
+
$?.success?.should == false
|
68
|
+
end
|
69
|
+
|
70
|
+
it "can exec given commands with ENV['TEST_ENV_NUM']" do
|
71
|
+
result = `#{executable} -e 'ruby -e "print ENV[:TEST_ENV_NUMBER.to_s].to_i"' -n 4`
|
72
|
+
result.gsub('"','').split('').sort.should == %w[0 2 3 4]
|
73
|
+
end
|
74
|
+
|
75
|
+
it "can exec given command non-parallel" do
|
76
|
+
result = `#{executable} -e 'ruby -e "sleep(rand(10)/100.0); puts ENV[:TEST_ENV_NUMBER.to_s].inspect"' -n 4 --non-parallel`
|
77
|
+
result.split("\n").should == %w["" "2" "3" "4"]
|
78
|
+
end
|
79
|
+
|
80
|
+
it "exists with success if all sub-processes returned success" do
|
81
|
+
system("#{executable} -e 'cat /dev/null' -n 4").should == true
|
82
|
+
end
|
83
|
+
|
84
|
+
it "exists with failure if any sub-processes returned failure" do
|
85
|
+
system("#{executable} -e 'test -e xxxx' -n 4").should == false
|
86
|
+
end
|
87
|
+
|
88
|
+
it "can run through parallel_spec / parallel_cucumber" do
|
89
|
+
version = `#{executable} -v`
|
90
|
+
`#{bin_folder}/parallel_spec -v`.should == version
|
91
|
+
`#{bin_folder}/parallel_cucumber -v`.should == version
|
92
|
+
end
|
93
|
+
|
94
|
+
it "runs faster with more processes" do
|
95
|
+
2.times{|i|
|
96
|
+
write "spec/xxx#{i}_spec.rb", 'describe("it"){it("should"){sleep 5}}; $stderr.puts ENV["TEST_ENV_NUMBER"]'
|
97
|
+
}
|
98
|
+
t = Time.now
|
99
|
+
run_tests(:processes => 2)
|
100
|
+
expected = 10
|
101
|
+
(Time.now - t).should <= expected
|
102
|
+
end
|
103
|
+
|
104
|
+
it "can can with given files" do
|
105
|
+
write "spec/x1_spec.rb", "puts '111'"
|
106
|
+
write "spec/x2_spec.rb", "puts '222'"
|
107
|
+
write "spec/x3_spec.rb", "puts '333'"
|
108
|
+
result = run_tests(:add => 'spec/x1_spec.rb spec/x3_spec.rb')
|
109
|
+
result.should include('111')
|
110
|
+
result.should include('333')
|
111
|
+
result.should_not include('222')
|
112
|
+
end
|
113
|
+
|
114
|
+
it "can run with test-options" do
|
115
|
+
write "spec/x1_spec.rb", ""
|
116
|
+
write "spec/x2_spec.rb", ""
|
117
|
+
result = run_tests(:add => "--test-options ' --version'", :processes => 2)
|
118
|
+
result.should =~ /\d+\.\d+\.\d+.*\d+\.\d+\.\d+/m # prints version twice
|
119
|
+
end
|
120
|
+
|
121
|
+
it "runs with test::unit" do
|
122
|
+
write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
|
123
|
+
result = run_tests(:type => :test)
|
124
|
+
result.should include('1 test')
|
125
|
+
$?.success?.should == true
|
126
|
+
end
|
127
|
+
|
128
|
+
it "passes test options to test::unit" do
|
129
|
+
write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
|
130
|
+
result = run_tests(:type => :test, :add => '--test-options "-v"')
|
131
|
+
result.should include('test_xxx') # verbose output of every test
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ParallelSpecs::SpecFailuresLogger do
|
4
|
+
def silence_warnings
|
5
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
6
|
+
yield
|
7
|
+
ensure
|
8
|
+
$VERBOSE = old_verbose
|
9
|
+
end
|
10
|
+
|
11
|
+
before do
|
12
|
+
@output = OutputLogger.new([])
|
13
|
+
@example1 = mock( 'example', :location => "#{Dir.pwd}/spec/path/to/example:123", :full_description => 'should do stuff', :description => 'd' )
|
14
|
+
@example2 = mock( 'example', :location => "#{Dir.pwd}/spec/path/to/example2:456", :full_description => 'should do other stuff', :description => 'd')
|
15
|
+
@exception1 = mock( :to_s => 'exception', :backtrace => [ '/path/to/error/line:33' ] )
|
16
|
+
@failure1 = mock( 'example', :location => "#{Dir.pwd}/example:123", :header => 'header', :exception => @exception1 )
|
17
|
+
@logger = ParallelSpecs::SpecFailuresLogger.new( @output )
|
18
|
+
end
|
19
|
+
|
20
|
+
after do
|
21
|
+
silence_warnings{ ParallelSpecs::SpecLoggerBase::RSPEC_1 = false }
|
22
|
+
end
|
23
|
+
|
24
|
+
def clean_output
|
25
|
+
@output.output.join("\n").gsub(/\e\[\d+m/,'')
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should produce a list of command lines for failing examples" do
|
29
|
+
@logger.example_failed @example1
|
30
|
+
@logger.example_failed @example2
|
31
|
+
|
32
|
+
@logger.dump_failures
|
33
|
+
@logger.dump_summary(1,2,3,4)
|
34
|
+
|
35
|
+
clean_output.should =~ /^rspec .*? should do stuff/
|
36
|
+
clean_output.should =~ /^rspec .*? should do other stuff/
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should invoke spec for rspec 1" do
|
40
|
+
silence_warnings{ ParallelSpecs::SpecLoggerBase::RSPEC_1 = true }
|
41
|
+
ParallelSpecs.stub!(:bundler_enabled?).and_return true
|
42
|
+
ParallelSpecs.stub!(:run).with("bundle show rspec").and_return "/foo/bar/rspec-1.0.2"
|
43
|
+
@logger.example_failed @example1
|
44
|
+
|
45
|
+
@logger.dump_failures
|
46
|
+
@logger.dump_summary(1,2,3,4)
|
47
|
+
|
48
|
+
clean_output.should =~ /^bundle exec spec/
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should invoke rspec for rspec 2" do
|
52
|
+
ParallelSpecs.stub!(:bundler_enabled?).and_return true
|
53
|
+
ParallelSpecs.stub!(:run).with("bundle show rspec").and_return "/foo/bar/rspec-2.0.2"
|
54
|
+
@logger.example_failed @example1
|
55
|
+
|
56
|
+
@logger.dump_failures
|
57
|
+
@logger.dump_summary(1,2,3,4)
|
58
|
+
|
59
|
+
clean_output.should =~ /^rspec/
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return relative paths" do
|
63
|
+
@logger.example_failed @example1
|
64
|
+
@logger.example_failed @example2
|
65
|
+
|
66
|
+
@logger.dump_failures
|
67
|
+
@logger.dump_summary(1,2,3,4)
|
68
|
+
|
69
|
+
clean_output.should =~ %r(\./spec/path/to/example:123)
|
70
|
+
clean_output.should =~ %r(\./spec/path/to/example2:456)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# should not longer be a problem since its using native rspec methods
|
75
|
+
xit "should not log examples without location" do
|
76
|
+
example = mock('example', :location => 'bla', :full_description => 'before :all')
|
77
|
+
@logger.example_failed example
|
78
|
+
@logger.dump_failures
|
79
|
+
@logger.dump_summary(1,2,3,4)
|
80
|
+
clean_output.should == ''
|
81
|
+
end
|
82
|
+
end
|