parallel_tests 0.7.0.alpha2 → 0.7.0.rc1
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/Gemfile.lock +2 -2
- data/Readme.md +4 -3
- data/ReadmeRails2.md +5 -1
- data/lib/parallel_tests.rb +1 -0
- data/lib/parallel_tests/cli.rb +51 -31
- data/lib/parallel_tests/railtie.rb +5 -7
- data/lib/parallel_tests/spec/runner.rb +1 -2
- data/lib/parallel_tests/tasks.rb +1 -1
- data/lib/parallel_tests/test/runner.rb +5 -7
- data/lib/parallel_tests/version.rb +1 -1
- data/spec/integration_spec.rb +10 -0
- data/spec/parallel_tests/test/runner_spec.rb +82 -30
- metadata +51 -30
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
parallel_tests (0.7.0.
|
4
|
+
parallel_tests (0.7.0.rc1)
|
5
5
|
parallel
|
6
6
|
|
7
7
|
GEM
|
@@ -18,7 +18,7 @@ GEM
|
|
18
18
|
gherkin (2.7.6)
|
19
19
|
json (>= 1.4.6)
|
20
20
|
json (1.6.4)
|
21
|
-
parallel (0.5.
|
21
|
+
parallel (0.5.2)
|
22
22
|
rake (0.8.7)
|
23
23
|
rspec (2.6.0)
|
24
24
|
rspec-core (~> 2.6.0)
|
data/Readme.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
Speedup Test::Unit + RSpec + Cucumber by running parallel on multiple CPUs (or cores).<br/>
|
2
2
|
ParallelTests splits tests into even groups(by number of tests or runtime) and runs each group in a single process with its own database.
|
3
3
|
|
4
|
+
[upgrading from 0.6 ?](https://github.com/grosser/parallel_tests/wiki/Upgrading-0.6.x-to-0.7.x)
|
5
|
+
|
4
6
|
Setup for Rails
|
5
7
|
===============
|
6
8
|
[still using Rails 2?](https://github.com/grosser/parallel_tests/blob/master/ReadmeRails2.md)
|
@@ -46,7 +48,7 @@ ParallelTests uses 1 database per test-process, 2 processes will use `*_test` an
|
|
46
48
|
|
47
49
|
Test by pattern (e.g. use one integration server per subfolder / see if you broke any 'user'-related tests)
|
48
50
|
|
49
|
-
rake parallel:test[^unit] #
|
51
|
+
rake parallel:test[^test/unit] # every test file in test/unit folder
|
50
52
|
rake parallel:test[user] # run users_controller + user_helper + user tests
|
51
53
|
rake parallel:test['user|product'] # run user and product related tests
|
52
54
|
|
@@ -73,7 +75,7 @@ Rspec: Add to your `.rspec_parallel` (or `.rspec`) :
|
|
73
75
|
RSpec
|
74
76
|
If installed as plugin: -I vendor/plugins/parallel_tests/lib
|
75
77
|
--format progress
|
76
|
-
--format ParallelTests::Spec::RuntimeLogger --out tmp/
|
78
|
+
--format ParallelTests::Spec::RuntimeLogger --out tmp/parallel_runtime_spec.log
|
77
79
|
|
78
80
|
Test::Unit: Add to your `test_helper.rb`:
|
79
81
|
|
@@ -168,7 +170,6 @@ TIPS
|
|
168
170
|
TODO
|
169
171
|
====
|
170
172
|
- document how to use cucumber runtime logger
|
171
|
-
- unify runtime-log location
|
172
173
|
- add tests for cucumber runtime formatter
|
173
174
|
- make jRuby compatible [basics](http://yehudakatz.com/2009/07/01/new-rails-isolation-testing/)
|
174
175
|
- make windows compatible
|
data/ReadmeRails2.md
CHANGED
@@ -19,13 +19,17 @@ OR as plugin
|
|
19
19
|
|
20
20
|
./script/plugin install git://github.com/grosser/parallel_tests.git
|
21
21
|
|
22
|
+
# add to Rakefile
|
23
|
+
begin; require 'vendor/plugins/parallel_tests/lib/parallel_tests/tasks'; rescue LoadError; end
|
24
|
+
|
25
|
+
|
22
26
|
Even process runtimes
|
23
27
|
-----------------
|
24
28
|
|
25
29
|
RSpec 1.x:
|
26
30
|
--format progress
|
27
31
|
--require parallel_tests/spec/runtime_logger
|
28
|
-
--format ParallelTests::Spec::RuntimeLogger:tmp/
|
32
|
+
--format ParallelTests::Spec::RuntimeLogger:tmp/parallel_runtime_spec.log
|
29
33
|
|
30
34
|
SpecSummaryLogger
|
31
35
|
--------------------
|
data/lib/parallel_tests.rb
CHANGED
data/lib/parallel_tests/cli.rb
CHANGED
@@ -5,7 +5,6 @@ module ParallelTest
|
|
5
5
|
module CLI
|
6
6
|
def self.run(argv)
|
7
7
|
options = parse_options!(argv)
|
8
|
-
test_results = nil
|
9
8
|
|
10
9
|
num_processes = ParallelTests.determine_number_of_processes(options[:count])
|
11
10
|
num_processes = num_processes * (options[:multiply] || 1)
|
@@ -13,40 +12,61 @@ module ParallelTest
|
|
13
12
|
if options[:execute]
|
14
13
|
execute_shell_command_in_parallel(options[:execute], num_processes, options)
|
15
14
|
else
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
runner.run_tests(group, groups.index(group), options)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
#parse and print results
|
38
|
-
results = runner.find_results(test_results.map { |result| result[:stdout] }*"")
|
39
|
-
puts ""
|
40
|
-
puts runner.summarize_results(results)
|
15
|
+
run_tests_in_parallel(num_processes, options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def self.run_tests_in_parallel(num_processes, options)
|
22
|
+
test_results = nil
|
23
|
+
lib = options[:type] || 'test'
|
24
|
+
runner = load_runner_for(lib)
|
25
|
+
|
26
|
+
report_time_taken do
|
27
|
+
groups = runner.tests_in_groups(options[:files], num_processes, options)
|
28
|
+
report_number_of_tests runner, groups
|
29
|
+
|
30
|
+
test_results = Parallel.map(groups, :in_processes => groups.size) do |group|
|
31
|
+
run_tests(runner, group, groups.index(group), options)
|
41
32
|
end
|
42
33
|
|
43
|
-
|
44
|
-
failed = test_results.any? { |result| result[:exit_status] != 0 }
|
45
|
-
abort "#{lib.capitalize}s Failed" if failed
|
34
|
+
report_results runner, test_results
|
46
35
|
end
|
36
|
+
|
37
|
+
abort "#{lib.capitalize}s Failed" if any_test_failed?(test_results)
|
47
38
|
end
|
48
39
|
|
49
|
-
|
40
|
+
def self.run_tests(runner, group, process_number, options)
|
41
|
+
if group.empty?
|
42
|
+
{:stdout => '', :exit_status => 0}
|
43
|
+
else
|
44
|
+
runner.run_tests(group, process_number, options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.report_results(runner, test_results)
|
49
|
+
results = runner.find_results(test_results.map { |result| result[:stdout] }*"")
|
50
|
+
puts ""
|
51
|
+
puts runner.summarize_results(results)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.report_number_of_tests(runner, groups)
|
55
|
+
name = runner.test_file_name
|
56
|
+
num_processes = groups.size
|
57
|
+
num_tests = groups.map(&:size).inject(:+)
|
58
|
+
puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
|
59
|
+
end
|
60
|
+
|
61
|
+
#exit with correct status code so rake parallel:test && echo 123 works
|
62
|
+
def self.any_test_failed?(test_results)
|
63
|
+
test_results.any? { |result| result[:exit_status] != 0 }
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.load_runner_for(lib)
|
67
|
+
require "parallel_tests/#{lib}/runner"
|
68
|
+
eval("ParallelTests::#{lib.capitalize}::Runner")
|
69
|
+
end
|
50
70
|
|
51
71
|
def self.parse_options!(argv)
|
52
72
|
options = {}
|
@@ -60,7 +80,7 @@ Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2',
|
|
60
80
|
Options are:
|
61
81
|
BANNER
|
62
82
|
opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
|
63
|
-
opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern") { |pattern| options[:pattern] = pattern }
|
83
|
+
opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern") { |pattern| options[:pattern] = /#{pattern}/ }
|
64
84
|
opts.on("--no-sort", "do not sort files before running them") { |no_sort| options[:no_sort] = no_sort }
|
65
85
|
opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run") { |multiply| options[:multiply] = multiply }
|
66
86
|
opts.on("-s [PATTERN]", "--single [PATTERN]", "Run all matching files in only one process") do |pattern|
|
@@ -1,10 +1,8 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
load File.expand_path("../tasks.rake", __FILE__)
|
7
|
-
end
|
1
|
+
# rake tasks for Rails 3+
|
2
|
+
module ParallelTests
|
3
|
+
class Railtie < ::Rails::Railtie
|
4
|
+
rake_tasks do
|
5
|
+
require "parallel_tests/tasks"
|
8
6
|
end
|
9
7
|
end
|
10
8
|
end
|
@@ -22,9 +22,8 @@ module ParallelTests
|
|
22
22
|
cmd or raise("Can't find executables rspec or spec")
|
23
23
|
end
|
24
24
|
|
25
|
-
# legacy <-> people log to this file using rspec options
|
26
25
|
def self.runtime_log
|
27
|
-
'tmp/
|
26
|
+
'tmp/parallel_runtime_spec.log'
|
28
27
|
end
|
29
28
|
|
30
29
|
def self.test_file_name
|
data/lib/parallel_tests/tasks.rb
CHANGED
@@ -49,7 +49,7 @@ namespace :parallel do
|
|
49
49
|
count, pattern, options = ParallelTests.parse_rake_args(args)
|
50
50
|
test_type = (type == 'features' ? 'cucumber' : type)
|
51
51
|
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
52
|
-
command = "#{executable}
|
52
|
+
command = "#{executable} #{type} --type #{test_type} -n #{count} -p '#{pattern}' -o '#{options}'"
|
53
53
|
abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
|
54
54
|
end
|
55
55
|
end
|
@@ -49,7 +49,7 @@ module ParallelTests
|
|
49
49
|
|
50
50
|
def self.find_results(test_output)
|
51
51
|
test_output.split("\n").map {|line|
|
52
|
-
line = line.gsub(/\.|F|\*/,'')
|
52
|
+
line = line.gsub(/\.|F|\*/,'').gsub(/\e\[\d+m/,'')
|
53
53
|
next unless line_is_result?(line)
|
54
54
|
line
|
55
55
|
}.compact
|
@@ -121,20 +121,18 @@ module ParallelTests
|
|
121
121
|
def self.find_tests(tests, options={})
|
122
122
|
(tests||[]).map do |file_or_folder|
|
123
123
|
if File.directory?(file_or_folder)
|
124
|
-
|
124
|
+
files = files_in_folder(file_or_folder)
|
125
|
+
files.grep(/#{Regexp.escape test_suffix}$/).grep(options[:pattern]||//)
|
125
126
|
else
|
126
127
|
file_or_folder
|
127
128
|
end
|
128
129
|
end.flatten.uniq
|
129
130
|
end
|
130
131
|
|
131
|
-
def self.
|
132
|
+
def self.files_in_folder(folder)
|
132
133
|
# follow one symlink and direct children
|
133
134
|
# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
|
134
|
-
|
135
|
-
files = files.map{|f| f.sub(folder+'/','') }
|
136
|
-
files = files.grep(/#{options[:pattern]}/)
|
137
|
-
files.map{|f| "#{folder}/#{f}" }
|
135
|
+
Dir[File.join(folder, "**{,/*/**}/*")].uniq
|
138
136
|
end
|
139
137
|
end
|
140
138
|
end
|
data/spec/integration_spec.rb
CHANGED
@@ -143,6 +143,16 @@ describe 'CLI' do
|
|
143
143
|
result.scan(/ENV-.?-/).should =~ ["ENV--", "ENV-2-", "ENV-3-", "ENV-4-", "ENV-5-"]
|
144
144
|
end
|
145
145
|
|
146
|
+
it "filters test by given pattern and relative paths" do
|
147
|
+
write "spec/x_spec.rb", "puts 'XXX'"
|
148
|
+
write "spec/y_spec.rb", "puts 'YYY'"
|
149
|
+
write "spec/z_spec.rb", "puts 'ZZZ'"
|
150
|
+
result = run_tests("spec", :add => '-p "^spec/(x|z)"')
|
151
|
+
result.should include('XXX')
|
152
|
+
result.should_not include('YYY')
|
153
|
+
result.should include('ZZZ')
|
154
|
+
end
|
155
|
+
|
146
156
|
context "Test::Unit" do
|
147
157
|
it "runs" do
|
148
158
|
write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
|
@@ -97,6 +97,13 @@ EOF
|
|
97
97
|
|
98
98
|
call(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors','14 tedsts, 20 assertions, 0 failures, 0 errors']
|
99
99
|
end
|
100
|
+
|
101
|
+
it "ignores color-codes" do
|
102
|
+
output = <<EOF
|
103
|
+
10 tests, 20 assertions, 0 \e[31mfailures, 0 errors
|
104
|
+
EOF
|
105
|
+
call(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors']
|
106
|
+
end
|
100
107
|
end
|
101
108
|
|
102
109
|
describe :find_tests do
|
@@ -104,45 +111,90 @@ EOF
|
|
104
111
|
ParallelTests::Test::Runner.send(:find_tests, *args)
|
105
112
|
end
|
106
113
|
|
107
|
-
|
114
|
+
def with_files(files)
|
108
115
|
begin
|
109
116
|
root = "/tmp/test-find_tests-#{rand(999)}"
|
110
117
|
`mkdir #{root}`
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
`touch #{root}/b/test.rb`
|
118
|
-
`ln -s #{root}/b #{root}/c`
|
119
|
-
`ln -s #{root}/b #{root}/a/`
|
120
|
-
call([root]).sort.should == [
|
121
|
-
"#{root}/a/b/y_test.rb",
|
122
|
-
"#{root}/a/x_test.rb",
|
123
|
-
"#{root}/b/y_test.rb",
|
124
|
-
"#{root}/c/y_test.rb",
|
125
|
-
"#{root}/x_test.rb"
|
126
|
-
]
|
118
|
+
files.each do |file|
|
119
|
+
parent = "#{root}/#{File.dirname(file)}"
|
120
|
+
`mkdir -p #{parent}` unless File.exist?(parent)
|
121
|
+
`touch #{root}/#{file}`
|
122
|
+
end
|
123
|
+
yield root
|
127
124
|
ensure
|
128
125
|
`rm -rf #{root}`
|
129
126
|
end
|
130
127
|
end
|
131
128
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
129
|
+
def inside_dir(dir)
|
130
|
+
old = Dir.pwd
|
131
|
+
Dir.chdir dir
|
132
|
+
yield
|
133
|
+
ensure
|
134
|
+
Dir.chdir old
|
135
|
+
end
|
136
|
+
|
137
|
+
it "finds test in folders with appended /" do
|
138
|
+
with_files(['b/a_test.rb']) do |root|
|
139
|
+
call(["#{root}/"]).sort.should == [
|
140
|
+
"#{root}/b/a_test.rb",
|
143
141
|
]
|
144
|
-
|
145
|
-
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
it "finds test files nested in symlinked folders" do
|
146
|
+
with_files(['a/a_test.rb','b/b_test.rb']) do |root|
|
147
|
+
`ln -s #{root}/a #{root}/b/link`
|
148
|
+
call(["#{root}/b"]).sort.should == [
|
149
|
+
"#{root}/b/b_test.rb",
|
150
|
+
"#{root}/b/link/a_test.rb",
|
151
|
+
]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it "finds test files nested in different folders" do
|
156
|
+
with_files(['a/a_test.rb','b/b_test.rb', 'c/c_test.rb']) do |root|
|
157
|
+
call(["#{root}/a", "#{root}/b"]).sort.should == [
|
158
|
+
"#{root}/a/a_test.rb",
|
159
|
+
"#{root}/b/b_test.rb",
|
160
|
+
]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
it "only finds tests in folders" do
|
165
|
+
with_files(['a/a_test.rb', 'a/test.rb', 'a/test_helper.rb']) do |root|
|
166
|
+
call(["#{root}/a"]).sort.should == [
|
167
|
+
"#{root}/a/a_test.rb"
|
168
|
+
]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
it "finds tests in nested folders" do
|
173
|
+
with_files(['a/b/c/d/a_test.rb']) do |root|
|
174
|
+
call(["#{root}/a"]).sort.should == [
|
175
|
+
"#{root}/a/b/c/d/a_test.rb"
|
176
|
+
]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
it "does not expand paths" do
|
181
|
+
with_files(['a/x_test.rb']) do |root|
|
182
|
+
inside_dir root do
|
183
|
+
call(['a']).sort.should == [
|
184
|
+
"a/x_test.rb"
|
185
|
+
]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
it "finds test files in folders by pattern" do
|
191
|
+
with_files(['a/x_test.rb','a/y_test.rb','a/z_test.rb']) do |root|
|
192
|
+
inside_dir root do
|
193
|
+
call(["a"], :pattern => /^a\/(y|z)_test/).sort.should == [
|
194
|
+
"a/y_test.rb",
|
195
|
+
"a/z_test.rb",
|
196
|
+
]
|
197
|
+
end
|
146
198
|
end
|
147
199
|
end
|
148
200
|
|
metadata
CHANGED
@@ -1,36 +1,49 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: parallel_tests
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 161060265
|
5
5
|
prerelease: 6
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 7
|
9
|
+
- 0
|
10
|
+
- rc
|
11
|
+
- 1
|
12
|
+
version: 0.7.0.rc1
|
6
13
|
platform: ruby
|
7
|
-
authors:
|
14
|
+
authors:
|
8
15
|
- Michael Grosser
|
9
16
|
autorequire:
|
10
17
|
bindir: bin
|
11
18
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
|
20
|
+
date: 2012-02-26 00:00:00 Z
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
17
24
|
none: false
|
18
|
-
requirements:
|
19
|
-
- -
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
|
22
|
-
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
23
32
|
prerelease: false
|
24
|
-
|
33
|
+
requirement: *id001
|
34
|
+
type: :runtime
|
35
|
+
name: parallel
|
25
36
|
description:
|
26
37
|
email: michael@grosser.it
|
27
|
-
executables:
|
38
|
+
executables:
|
28
39
|
- parallel_cucumber
|
29
40
|
- parallel_spec
|
30
41
|
- parallel_test
|
31
42
|
extensions: []
|
43
|
+
|
32
44
|
extra_rdoc_files: []
|
33
|
-
|
45
|
+
|
46
|
+
files:
|
34
47
|
- .gitignore
|
35
48
|
- Gemfile
|
36
49
|
- Gemfile.lock
|
@@ -67,31 +80,39 @@ files:
|
|
67
80
|
- spec/parallel_tests_spec.rb
|
68
81
|
- spec/spec_helper.rb
|
69
82
|
homepage: http://github.com/grosser/parallel_tests
|
70
|
-
licenses:
|
83
|
+
licenses:
|
71
84
|
- MIT
|
72
85
|
post_install_message:
|
73
86
|
rdoc_options: []
|
74
|
-
|
87
|
+
|
88
|
+
require_paths:
|
75
89
|
- lib
|
76
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
91
|
none: false
|
78
|
-
requirements:
|
79
|
-
- -
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
|
82
|
-
segments:
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 3
|
96
|
+
segments:
|
83
97
|
- 0
|
84
|
-
|
85
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
100
|
none: false
|
87
|
-
requirements:
|
88
|
-
- -
|
89
|
-
- !ruby/object:Gem::Version
|
101
|
+
requirements:
|
102
|
+
- - ">"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 25
|
105
|
+
segments:
|
106
|
+
- 1
|
107
|
+
- 3
|
108
|
+
- 1
|
90
109
|
version: 1.3.1
|
91
110
|
requirements: []
|
111
|
+
|
92
112
|
rubyforge_project:
|
93
113
|
rubygems_version: 1.8.15
|
94
114
|
signing_key:
|
95
115
|
specification_version: 3
|
96
116
|
summary: Run Test::Unit / RSpec / Cucumber in parallel
|
97
117
|
test_files: []
|
118
|
+
|