forked from grosser/parallel_tests
/
parallel_tests.rb
141 lines (118 loc) · 3.68 KB
/
parallel_tests.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
require 'parallel'
require 'parallel_tests/grouper'
require 'parallel_tests/railtie'
class ParallelTests
VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
# parallel:spec[2,controller] <-> parallel:spec[controller]
def self.parse_rake_args(args)
num_processes = Parallel.processor_count
options = ""
if args[:count].to_s =~ /^\d*$/ # number or empty
num_processes = args[:count] unless args[:count].to_s.empty?
prefix = args[:path_prefix]
options = args[:options] if args[:options]
else # something stringy
prefix = args[:count]
end
[num_processes.to_i, prefix.to_s, options]
end
# finds all tests and partitions them into groups
def self.tests_in_groups(root, num_groups, options={})
if options[:no_sort] == true
Grouper.in_groups(find_tests(root), num_groups)
else
Grouper.in_even_groups_by_size(tests_with_runtime(root), num_groups)
end
end
def self.run_tests(test_files, process_number, options)
require_list = test_files.map { |filename| "\"#{filename}\"" }.join(",")
cmd = "ruby -Itest #{options[:test_options]} -e '[#{require_list}].each {|f| require f }'"
execute_command(cmd, process_number, options)
end
def self.execute_command(cmd, process_number, options)
cmd = "TEST_ENV_NUMBER=#{test_env_number(process_number)} ; export TEST_ENV_NUMBER; #{cmd}"
f = open("|#{cmd}", 'r')
output = fetch_output(f, options)
f.close
{:stdout => output, :exit_status => $?.exitstatus}
end
def self.find_results(test_output)
test_output.split("\n").map {|line|
line = line.gsub(/\.|F|\*/,'')
next unless line_is_result?(line)
line
}.compact
end
def self.test_env_number(process_number)
process_number == 0 ? '' : process_number + 1
end
def self.runtime_log
'__foo__'
end
protected
# read output of the process and print in in chucks
def self.fetch_output(process, options)
all = ''
buffer = ''
timeout = options[:chunk_timeout] || 0.2
flushed = Time.now.to_f
while char = process.getc
char = (char.is_a?(Fixnum) ? char.chr : char) # 1.8 <-> 1.9
all << char
# print in chunks so large blocks stay together
now = Time.now.to_f
buffer << char
if flushed + timeout < now
print buffer
STDOUT.flush
buffer = ''
flushed = now
end
end
# print the remainder
print buffer
STDOUT.flush
all
end
# copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
def self.bundler_enabled?
return true if Object.const_defined?(:Bundler)
previous = nil
current = File.expand_path(Dir.pwd)
until !File.directory?(current) || current == previous
filename = File.join(current, "Gemfile")
return true if File.exists?(filename)
current, previous = File.expand_path("..", current), current
end
false
end
def self.line_is_result?(line)
line =~ /\d+ failure/
end
def self.test_suffix
"_test.rb"
end
def self.tests_with_runtime(root)
tests = find_tests(root)
lines = File.read(runtime_log).split("\n") rescue []
# use recorded test runtime if we got enough data
if lines.size * 1.5 > tests.size
puts "Using recorded test runtime"
times = Hash.new(1)
lines.each do |line|
test, time = line.split(":")
times[test] = time.to_f
end
tests.sort.map{|test| [test, times[test]] }
else # use file sizes
tests.sort.map{|test| [test, File.stat(test).size] }
end
end
def self.find_tests(root)
if root.is_a?(Array)
root
else
Dir["#{root}**/**/*#{self.test_suffix}"]
end
end
end