parallel_tests 0.6.20 → 0.7.0.alpha
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.
- data/.gitignore +1 -0
- data/Gemfile +2 -4
- data/Gemfile.lock +7 -7
- data/Rakefile +18 -16
- data/Readme.md +15 -12
- data/bin/parallel_test +2 -98
- data/lib/parallel_tests.rb +2 -125
- data/lib/parallel_tests/cli.rb +102 -0
- data/lib/parallel_tests/cucumber/runner.rb +40 -0
- data/lib/parallel_tests/cucumber/runtime_logger.rb +58 -0
- data/lib/parallel_tests/grouper.rb +2 -2
- data/lib/parallel_tests/railtie.rb +3 -3
- data/lib/{parallel_specs/spec_failures_logger.rb → parallel_tests/spec/failures_logger.rb} +4 -3
- data/lib/{parallel_specs/spec_logger_base.rb → parallel_tests/spec/logger_base.rb} +7 -3
- data/lib/parallel_tests/spec/runner.rb +56 -0
- data/lib/{parallel_specs/spec_runtime_logger.rb → parallel_tests/spec/runtime_logger.rb} +2 -2
- data/lib/{parallel_specs/spec_summary_logger.rb → parallel_tests/spec/summary_logger.rb} +2 -2
- data/lib/parallel_tests/tasks.rb +0 -25
- data/lib/parallel_tests/test/runner.rb +126 -0
- data/lib/parallel_tests/test/runtime_logger.rb +92 -0
- data/lib/parallel_tests/version.rb +3 -0
- data/parallel_tests.gemspec +10 -61
- data/spec/parallel_tests/cucumber/runner_spec.rb +76 -0
- data/spec/{parallel_specs/spec_failure_logger_spec.rb → parallel_tests/spec/failure_logger_spec.rb} +8 -8
- data/spec/parallel_tests/spec/runner_spec.rb +178 -0
- data/spec/{parallel_specs/spec_runtime_logger_spec.rb → parallel_tests/spec/runtime_logger_spec.rb} +4 -4
- data/spec/{parallel_specs/spec_summary_logger_spec.rb → parallel_tests/spec/summary_logger_spec.rb} +2 -2
- data/spec/parallel_tests/test/runner_spec.rb +179 -0
- data/spec/parallel_tests/{runtime_logger_spec.rb → test/runtime_logger_spec.rb} +19 -16
- data/spec/parallel_tests_spec.rb +2 -158
- data/spec/spec_helper.rb +9 -7
- metadata +30 -26
- data/VERSION +0 -1
- data/lib/parallel_cucumber.rb +0 -36
- data/lib/parallel_cucumber/runtime_logger.rb +0 -57
- data/lib/parallel_specs.rb +0 -52
- data/lib/parallel_tests/runtime_logger.rb +0 -78
- data/lib/tasks/parallel_tests.rake +0 -1
- data/spec/parallel_cucumber_spec.rb +0 -72
- data/spec/parallel_specs_spec.rb +0 -173
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'parallel_tests/test/runner'
|
2
|
+
|
3
|
+
module ParallelTests
|
4
|
+
module Cucumber
|
5
|
+
class Runner < ParallelTests::Test::Runner
|
6
|
+
def self.run_tests(test_files, process_number, options)
|
7
|
+
color = ($stdout.tty? ? 'AUTOTEST=1 ; export AUTOTEST ;' : '')#display color when we are in a terminal
|
8
|
+
runtime_logging = " --format ParallelCucumber::RuntimeLogger --out #{runtime_log}"
|
9
|
+
cmd = "#{color} #{executable}"
|
10
|
+
cmd << runtime_logging if File.directory?(File.dirname(runtime_log))
|
11
|
+
cmd << " #{options[:test_options]} #{test_files*' '}"
|
12
|
+
execute_command(cmd, process_number, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.executable
|
16
|
+
if ParallelTests.bundler_enabled?
|
17
|
+
"bundle exec cucumber"
|
18
|
+
elsif File.file?("script/cucumber")
|
19
|
+
"script/cucumber"
|
20
|
+
else
|
21
|
+
"cucumber"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.runtime_log
|
26
|
+
'tmp/parallel_runtime_cucumber.log'
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def self.test_suffix
|
32
|
+
".feature"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.line_is_result?(line)
|
36
|
+
line =~ /^\d+ (steps|scenarios)/
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module ParallelTests
|
2
|
+
module Cucumber
|
3
|
+
class RuntimeLogger
|
4
|
+
def initialize(step_mother, path_or_io, options=nil)
|
5
|
+
@io = prepare_io(path_or_io)
|
6
|
+
@example_times = Hash.new(0)
|
7
|
+
end
|
8
|
+
|
9
|
+
def before_feature(_)
|
10
|
+
@start_at = Time.now.to_f
|
11
|
+
end
|
12
|
+
|
13
|
+
def after_feature(feature)
|
14
|
+
@example_times[feature.file] += Time.now.to_f - @start_at
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_features(*args)
|
18
|
+
lock_output do
|
19
|
+
@io.puts @example_times.map { |file, time| "#{file}:#{time}" }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def prepare_io(path_or_io)
|
26
|
+
if path_or_io.respond_to?(:write)
|
27
|
+
path_or_io
|
28
|
+
else # its a path
|
29
|
+
File.open(path_or_io, 'w').close # clean out the file
|
30
|
+
file = File.open(path_or_io, 'a')
|
31
|
+
|
32
|
+
at_exit do
|
33
|
+
unless file.closed?
|
34
|
+
file.flush
|
35
|
+
file.close
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
file
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# do not let multiple processes get in each others way
|
44
|
+
def lock_output
|
45
|
+
if File === @io
|
46
|
+
begin
|
47
|
+
@io.flock File::LOCK_EX
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
@io.flock File::LOCK_UN
|
51
|
+
end
|
52
|
+
else
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
module ParallelTests
|
2
2
|
class Grouper
|
3
3
|
def self.in_groups(items, num_groups)
|
4
4
|
groups = Array.new(num_groups){ [] }
|
@@ -35,7 +35,7 @@ class ParallelTests
|
|
35
35
|
files.sort_by{|item, size| size }.reverse
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
private
|
39
39
|
|
40
40
|
def self.smallest_group(groups)
|
41
41
|
groups.min_by{|g| g[:size] }
|
@@ -1,9 +1,9 @@
|
|
1
|
-
# add rake tasks if we are inside Rails
|
1
|
+
# add rake tasks if we are inside Rails 3
|
2
2
|
if defined?(Rails::Railtie)
|
3
|
-
|
3
|
+
module ParallelTests
|
4
4
|
class Railtie < ::Rails::Railtie
|
5
5
|
rake_tasks do
|
6
|
-
load File.expand_path("
|
6
|
+
load File.expand_path("../tasks.rake", __FILE__)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'parallel_tests/spec/logger_base'
|
2
|
+
require 'parallel_tests/spec/runner'
|
2
3
|
|
3
|
-
class
|
4
|
+
class ParallelTests::Spec::FailuresLogger < ParallelTests::Spec::LoggerBase
|
4
5
|
# RSpec 1: does not keep track of failures, so we do
|
5
6
|
def example_failed(example, *args)
|
6
7
|
if RSPEC_1
|
@@ -37,7 +38,7 @@ class ParallelSpecs::SpecFailuresLogger < ParallelSpecs::SpecLoggerBase
|
|
37
38
|
file, line = example.location.to_s.split(':')
|
38
39
|
next unless file and line
|
39
40
|
file.gsub!(%r(^.*?/spec/), './spec/')
|
40
|
-
@output.puts "#{
|
41
|
+
@output.puts "#{ParallelTests::Spec::Runner.executable} #{file}:#{line} # #{example.description}"
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
module ParallelTests
|
2
|
+
module Spec
|
3
|
+
end
|
4
|
+
end
|
2
5
|
|
3
6
|
begin
|
4
7
|
require 'rspec/core/formatters/base_text_formatter'
|
@@ -7,9 +10,10 @@ rescue LoadError
|
|
7
10
|
require 'spec/runner/formatter/base_text_formatter'
|
8
11
|
base = Spec::Runner::Formatter::BaseTextFormatter
|
9
12
|
end
|
10
|
-
ParallelSpecs::SpecLoggerBaseBase = base
|
11
13
|
|
12
|
-
|
14
|
+
ParallelTests::Spec::LoggerBaseBase = base
|
15
|
+
|
16
|
+
class ParallelTests::Spec::LoggerBase < ParallelTests::Spec::LoggerBaseBase
|
13
17
|
RSPEC_1 = !defined?(RSpec::Core::Formatters::BaseTextFormatter) # do not test for Spec, this will trigger deprecation warning in rspec 2
|
14
18
|
|
15
19
|
def initialize(*args)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'parallel_tests/test/runner'
|
2
|
+
|
3
|
+
module ParallelTests
|
4
|
+
module Spec
|
5
|
+
class Runner < ParallelTests::Test::Runner
|
6
|
+
def self.run_tests(test_files, process_number, options)
|
7
|
+
exe = executable # expensive, so we cache
|
8
|
+
version = (exe =~ /\brspec\b/ ? 2 : 1)
|
9
|
+
cmd = "#{rspec_1_color if version == 1}#{exe} #{options[:test_options]} #{rspec_2_color if version == 2}#{spec_opts(version)} #{test_files*' '}"
|
10
|
+
execute_command(cmd, process_number, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.executable
|
14
|
+
cmd = if File.file?("script/spec")
|
15
|
+
"script/spec"
|
16
|
+
elsif ParallelTests.bundler_enabled?
|
17
|
+
cmd = (run("bundle show rspec") =~ %r{/rspec-1[^/]+$} ? "spec" : "rspec")
|
18
|
+
"bundle exec #{cmd}"
|
19
|
+
else
|
20
|
+
%w[spec rspec].detect{|cmd| system "#{cmd} --version > /dev/null 2>&1" }
|
21
|
+
end
|
22
|
+
cmd or raise("Can't find executables rspec or spec")
|
23
|
+
end
|
24
|
+
|
25
|
+
# legacy <-> people log to this file using rspec options
|
26
|
+
def self.runtime_log
|
27
|
+
'tmp/parallel_profile.log'
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# so it can be stubbed....
|
33
|
+
def self.run(cmd)
|
34
|
+
`#{cmd}`
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.rspec_1_color
|
38
|
+
'RSPEC_COLOR=1 ; export RSPEC_COLOR ;' if $stdout.tty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.rspec_2_color
|
42
|
+
'--color --tty ' if $stdout.tty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.spec_opts(rspec_version)
|
46
|
+
options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
|
47
|
+
return unless options_file
|
48
|
+
"-O #{options_file}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.test_suffix
|
52
|
+
"_spec.rb"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require '
|
1
|
+
require 'parallel_tests/spec/logger_base'
|
2
2
|
|
3
|
-
class
|
3
|
+
class ParallelTests::Spec::RuntimeLogger < ParallelTests::Spec::LoggerBase
|
4
4
|
def initialize(*args)
|
5
5
|
super
|
6
6
|
@example_times = Hash.new(0)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require '
|
1
|
+
require 'parallel_tests/spec/failures_logger'
|
2
2
|
|
3
|
-
class
|
3
|
+
class ParallelTests::Spec::SummaryLogger < ParallelTests::Spec::LoggerBase
|
4
4
|
# RSpec 1: dumps 1 failed spec
|
5
5
|
def dump_failure(*args)
|
6
6
|
lock_output do
|
data/lib/parallel_tests/tasks.rb
CHANGED
@@ -53,28 +53,3 @@ namespace :parallel do
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
57
|
-
#backwards compatability
|
58
|
-
#spec:parallel:prepare
|
59
|
-
#spec:parallel
|
60
|
-
#test:parallel
|
61
|
-
namespace :spec do
|
62
|
-
namespace :parallel do
|
63
|
-
task :prepare, :count do |t,args|
|
64
|
-
$stderr.puts "WARNING -- Deprecated! use parallel:prepare"
|
65
|
-
Rake::Task['parallel:prepare'].invoke(args[:count])
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
task :parallel, :count, :pattern do |t,args|
|
70
|
-
$stderr.puts "WARNING -- Deprecated! use parallel:spec"
|
71
|
-
Rake::Task['parallel:spec'].invoke(args[:count], args[:pattern])
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
namespace :test do
|
76
|
-
task :parallel, :count, :pattern do |t,args|
|
77
|
-
$stderr.puts "WARNING -- Deprecated! use parallel:test"
|
78
|
-
Rake::Task['parallel:test'].invoke(args[:count], args[:pattern])
|
79
|
-
end
|
80
|
-
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module ParallelTests
|
2
|
+
module Test
|
3
|
+
class Runner
|
4
|
+
# finds all tests and partitions them into groups
|
5
|
+
def self.tests_in_groups(root, num_groups, options={})
|
6
|
+
tests = find_tests(root, options)
|
7
|
+
if options[:no_sort] == true
|
8
|
+
Grouper.in_groups(tests, num_groups)
|
9
|
+
else
|
10
|
+
tests = with_runtime_info(tests)
|
11
|
+
Grouper.in_even_groups_by_size(tests, num_groups, options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.run_tests(test_files, process_number, options)
|
16
|
+
require_list = test_files.map { |filename| %{"#{File.expand_path filename}"} }.join(",")
|
17
|
+
cmd = "ruby -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"
|
18
|
+
execute_command(cmd, process_number, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.execute_command(cmd, process_number, options)
|
22
|
+
cmd = "TEST_ENV_NUMBER=#{test_env_number(process_number)} ; export TEST_ENV_NUMBER; #{cmd}"
|
23
|
+
f = open("|#{cmd}", 'r')
|
24
|
+
output = fetch_output(f, options)
|
25
|
+
f.close
|
26
|
+
{:stdout => output, :exit_status => $?.exitstatus}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.find_results(test_output)
|
30
|
+
test_output.split("\n").map {|line|
|
31
|
+
line = line.gsub(/\.|F|\*/,'')
|
32
|
+
next unless line_is_result?(line)
|
33
|
+
line
|
34
|
+
}.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.test_env_number(process_number)
|
38
|
+
process_number == 0 ? '' : process_number + 1
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.runtime_log
|
42
|
+
'tmp/parallel_runtime_test.log'
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.summarize_results(results)
|
46
|
+
results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
|
47
|
+
counts = results.scan(/(\d+) (\w+)/)
|
48
|
+
sums = counts.inject(Hash.new(0)) do |sum, (number, word)|
|
49
|
+
sum[word] += number.to_i
|
50
|
+
sum
|
51
|
+
end
|
52
|
+
sums.sort.map{|word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
# read output of the process and print in in chucks
|
58
|
+
def self.fetch_output(process, options)
|
59
|
+
all = ''
|
60
|
+
buffer = ''
|
61
|
+
timeout = options[:chunk_timeout] || 0.2
|
62
|
+
flushed = Time.now.to_f
|
63
|
+
|
64
|
+
while char = process.getc
|
65
|
+
char = (char.is_a?(Fixnum) ? char.chr : char) # 1.8 <-> 1.9
|
66
|
+
all << char
|
67
|
+
|
68
|
+
# print in chunks so large blocks stay together
|
69
|
+
now = Time.now.to_f
|
70
|
+
buffer << char
|
71
|
+
if flushed + timeout < now
|
72
|
+
$stdout.print buffer
|
73
|
+
$stdout.flush
|
74
|
+
buffer = ''
|
75
|
+
flushed = now
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# print the remainder
|
80
|
+
$stdout.print buffer
|
81
|
+
$stdout.flush
|
82
|
+
|
83
|
+
all
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.line_is_result?(line)
|
87
|
+
line =~ /\d+ failure/
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.test_suffix
|
91
|
+
"_test.rb"
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.with_runtime_info(tests)
|
95
|
+
lines = File.read(runtime_log).split("\n") rescue []
|
96
|
+
|
97
|
+
# use recorded test runtime if we got enough data
|
98
|
+
if lines.size * 1.5 > tests.size
|
99
|
+
puts "Using recorded test runtime"
|
100
|
+
times = Hash.new(1)
|
101
|
+
lines.each do |line|
|
102
|
+
test, time = line.split(":")
|
103
|
+
next unless test and time
|
104
|
+
times[File.expand_path(test)] = time.to_f
|
105
|
+
end
|
106
|
+
tests.sort.map{|test| [test, times[test]] }
|
107
|
+
else # use file sizes
|
108
|
+
tests.sort.map{|test| [test, File.stat(test).size] }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.find_tests(root, options={})
|
113
|
+
if root.is_a?(Array)
|
114
|
+
root
|
115
|
+
else
|
116
|
+
# follow one symlink and direct children
|
117
|
+
# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
|
118
|
+
files = Dir["#{root}/**{,/*/**}/*#{test_suffix}"].uniq
|
119
|
+
files = files.map{|f| f.sub(root+'/','') }
|
120
|
+
files = files.grep(/#{options[:pattern]}/)
|
121
|
+
files.map{|f| "#{root}/#{f}" }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'parallel_tests/test/runner'
|
2
|
+
|
3
|
+
module ParallelTests
|
4
|
+
module Test
|
5
|
+
class RuntimeLogger
|
6
|
+
@@has_started = false
|
7
|
+
|
8
|
+
def self.log(test, start_time, end_time)
|
9
|
+
return if test.is_a? ::Test::Unit::TestSuite # don't log for suites-of-suites
|
10
|
+
|
11
|
+
if !@@has_started # make empty log file
|
12
|
+
File.open(logfile, 'w'){}
|
13
|
+
@@has_started = true
|
14
|
+
end
|
15
|
+
|
16
|
+
locked_appending_to(logfile) do |file|
|
17
|
+
file.puts(message(test, start_time, end_time))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.message(test, start_time, end_time)
|
22
|
+
delta = "%.2f" % (end_time.to_f-start_time.to_f)
|
23
|
+
filename = class_directory(test.class) + class_to_filename(test.class) + ".rb"
|
24
|
+
"#{filename}:#{delta}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Note: this is a best guess at conventional test directory structure, and may need
|
28
|
+
# tweaking / post-processing to match correctly for any given project
|
29
|
+
def self.class_directory(suspect)
|
30
|
+
result = "test/"
|
31
|
+
|
32
|
+
if defined?(Rails)
|
33
|
+
result += case suspect.superclass.name
|
34
|
+
when "ActionDispatch::IntegrationTest"
|
35
|
+
"integration/"
|
36
|
+
when "ActionDispatch::PerformanceTest"
|
37
|
+
"performance/"
|
38
|
+
when "ActionController::TestCase"
|
39
|
+
"functional/"
|
40
|
+
when "ActionView::TestCase"
|
41
|
+
"unit/helpers/"
|
42
|
+
else
|
43
|
+
"unit/"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
# based on https://github.com/grosser/single_test/blob/master/lib/single_test.rb#L117
|
50
|
+
def self.class_to_filename(suspect)
|
51
|
+
word = suspect.to_s.dup
|
52
|
+
return word unless word.match /^[A-Z]/ and not word.match %r{/[a-z]}
|
53
|
+
|
54
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
55
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
56
|
+
word.gsub!(/\:\:/, '/')
|
57
|
+
word.tr!("-", "_")
|
58
|
+
word.downcase!
|
59
|
+
word
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.locked_appending_to(file)
|
63
|
+
File.open(file, 'a') do |f|
|
64
|
+
begin
|
65
|
+
f.flock File::LOCK_EX
|
66
|
+
yield f
|
67
|
+
ensure
|
68
|
+
f.flock File::LOCK_UN
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.logfile
|
74
|
+
ParallelTests::Test::Runner.runtime_log
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
require 'test/unit/testsuite'
|
81
|
+
class ::Test::Unit::TestSuite
|
82
|
+
alias :run_without_timing :run unless defined? @@timing_installed
|
83
|
+
|
84
|
+
def run(result, &progress_block)
|
85
|
+
start_time=Time.now
|
86
|
+
run_without_timing(result, &progress_block)
|
87
|
+
end_time=Time.now
|
88
|
+
ParallelTests::Test::RuntimeLogger.log(self.tests.first, start_time, end_time)
|
89
|
+
end
|
90
|
+
|
91
|
+
@@timing_installed = true
|
92
|
+
end
|