minitest-parallel_fork 1.0.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 -0
- data/CHANGELOG +3 -0
- data/MIT-LICENSE +18 -0
- data/README.rdoc +80 -0
- data/Rakefile +47 -0
- data/lib/minitest/parallel_fork.rb +94 -0
- data/spec/minitest_parallel_fork_example.rb +20 -0
- data/spec/minitest_parallel_fork_spec.rb +14 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 09d52a418fc9f248ded3902c4b7a5fd4b775cdfa
|
4
|
+
data.tar.gz: 36a3dcb18d5b7652af47c934c8a8a082a8ea78bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ccc49c0c045f2c9f8d3c6003373306fa75c787bee8b95eec0c401e9e9c5353f91635f52fce24714b7d2ffb7ab7f7e849abcb7dedaae0c3210009dbcb7e00b8f8
|
7
|
+
data.tar.gz: dd22675a8b0cbe257640b8c2ff518f30990b827ba8c02b1d33b89c49fa84d28fca37e5a9ba7e26fcdd7cdef87fb26d525ae1196dd373069cb0e944297ca0a9e9
|
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2015 Jeremy Evans
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
= minitest-parallel_fork
|
2
|
+
|
3
|
+
minitest-parallel_fork adds fork-based parallelization to Minitest. Each test/spec
|
4
|
+
suite is run one of the forks, allowing this to work correctly when using
|
5
|
+
before_all/after_all/around_all hooks provided by minitest-hooks. Using separate
|
6
|
+
processes via fork can significantly improve spec performance when using MRI,
|
7
|
+
and can work in cases where Minitest's default thread-based parallelism do not work,
|
8
|
+
such as when tests/specs modify the constant namespace.
|
9
|
+
|
10
|
+
= Installation
|
11
|
+
|
12
|
+
gem install minitest-parallel_fork
|
13
|
+
|
14
|
+
= Source Code
|
15
|
+
|
16
|
+
Source code is available on GitHub at https://github.com/jeremyevans/minitest-parallel_fork
|
17
|
+
|
18
|
+
= Usage
|
19
|
+
|
20
|
+
You can enable fork-based parallelism just by requiring +minitest/parallel_fork+. One easy
|
21
|
+
to do so without modifying the spec code itself is to use the +RUBYOPT+ environment variable.
|
22
|
+
So if you execute your specs using:
|
23
|
+
|
24
|
+
rake spec
|
25
|
+
|
26
|
+
You can switch to fork-based parallelism using:
|
27
|
+
|
28
|
+
RUBYOPT=-rminitest/parallel_fork rake spec
|
29
|
+
|
30
|
+
To control the number of forks, you can set the +NCPU+ environment variable:
|
31
|
+
|
32
|
+
NCPU=8 RUBYOPT=-rminitest/parallel_fork rake spec
|
33
|
+
|
34
|
+
If you don't set the +NCPU+ environment variable, minitest-parallel_fork will use
|
35
|
+
4 forks by default.
|
36
|
+
|
37
|
+
= Hooks
|
38
|
+
|
39
|
+
In some cases, especially when using external databases, you'll need to do some
|
40
|
+
before fork or after fork setup. minitest/parallel_fork supports +before_parallel_fork+
|
41
|
+
and +after_parallel_fork+ hooks.
|
42
|
+
|
43
|
+
+before_parallel_fork+ is called before any child processes are forked:
|
44
|
+
|
45
|
+
Minitest.before_parallel_fork do
|
46
|
+
DB.disconnect
|
47
|
+
end
|
48
|
+
|
49
|
+
+after_parallel_fork+ is called after each child process is forked, with the number
|
50
|
+
of the child process, starting at 0:
|
51
|
+
|
52
|
+
Minitest.after_parallel_fork do |i|
|
53
|
+
DB.opts[:database] += (i+1).to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
The above examples show a fairly easy way to use minitest-parallel_fork with an external
|
57
|
+
database when using Sequel. Before forking, all existing database connections are
|
58
|
+
disconnecting, and after forking, the database name is changed in each child to reference
|
59
|
+
a child-specific database, so that the child processes do not share a database and are
|
60
|
+
thus independent.
|
61
|
+
|
62
|
+
= Speedup
|
63
|
+
|
64
|
+
The speedup you get greatly depends on your specs. Here's some examples using Sequel's
|
65
|
+
specs:
|
66
|
+
|
67
|
+
2 forks 4 forks
|
68
|
+
spec_core: 1.25x - 1.36x 1.5x
|
69
|
+
spec_model: 1.29x - 1.62x 1.72x - 2.02x
|
70
|
+
spec_plugin: 1.57x - 1.76x 2.29x - 2.37x
|
71
|
+
spec_sqlite: 1.75x - 1.86x 2.26x - 2.65x
|
72
|
+
spec_postgres: 1.32x - 1.40x Untested
|
73
|
+
|
74
|
+
= License
|
75
|
+
|
76
|
+
MIT
|
77
|
+
|
78
|
+
= Author
|
79
|
+
|
80
|
+
Jeremy Evans <code@jeremyevans.net>
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/clean"
|
3
|
+
|
4
|
+
CLEAN.include ["minitest-parallel_fork-*.gem", "rdoc", "coverage"]
|
5
|
+
|
6
|
+
desc "Build minitest-parallel_fork gem"
|
7
|
+
task :package=>[:clean] do |p|
|
8
|
+
sh %{#{FileUtils::RUBY} -S gem build minitest-parallel_fork.gemspec}
|
9
|
+
end
|
10
|
+
|
11
|
+
### Specs
|
12
|
+
|
13
|
+
desc "Run specs"
|
14
|
+
task :spec do
|
15
|
+
ENV['RUBY'] = FileUtils::RUBY
|
16
|
+
ENV['RUBYOPT'] = "#{ENV['RUBYOPT']} -rubygems"
|
17
|
+
sh %{#{FileUtils::RUBY} spec/minitest_parallel_fork_spec.rb}
|
18
|
+
end
|
19
|
+
|
20
|
+
task :default=>:spec
|
21
|
+
|
22
|
+
### RDoc
|
23
|
+
|
24
|
+
RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'minitest-parallel_fork: fork-based parallelization for minitest']
|
25
|
+
|
26
|
+
begin
|
27
|
+
gem 'hanna-nouveau'
|
28
|
+
RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
|
29
|
+
rescue Gem::LoadError
|
30
|
+
end
|
31
|
+
|
32
|
+
rdoc_task_class = begin
|
33
|
+
require "rdoc/task"
|
34
|
+
RDoc::Task
|
35
|
+
rescue LoadError
|
36
|
+
require "rake/rdoctask"
|
37
|
+
Rake::RDocTask
|
38
|
+
end
|
39
|
+
|
40
|
+
RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
|
41
|
+
|
42
|
+
rdoc_task_class.new do |rdoc|
|
43
|
+
rdoc.rdoc_dir = "rdoc"
|
44
|
+
rdoc.options += RDOC_OPTS
|
45
|
+
rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb"
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
gem 'minitest'
|
2
|
+
require 'minitest'
|
3
|
+
|
4
|
+
module Minitest
|
5
|
+
# Set the before_parallel_fork block to the given block
|
6
|
+
def self.before_parallel_fork(&block)
|
7
|
+
@before_parallel_fork = block
|
8
|
+
end
|
9
|
+
|
10
|
+
# Set the after_parallel_fork block to the given block
|
11
|
+
def self.after_parallel_fork(i=nil, &block)
|
12
|
+
@after_parallel_fork = block
|
13
|
+
end
|
14
|
+
|
15
|
+
# Subclass of Assertion for unexpected errors. UnexpectedError
|
16
|
+
# can not be used as it can include undumpable objects. This
|
17
|
+
# class converts all data it needs to plain strings, so that
|
18
|
+
# it will be dumpable.
|
19
|
+
class DumpableUnexpectedError < Assertion # :nodoc:
|
20
|
+
attr_accessor :backtrace
|
21
|
+
|
22
|
+
def initialize(unexpected)
|
23
|
+
exception_class_name = unexpected.exception.class.name.to_s
|
24
|
+
exception_message = unexpected.exception.message.to_s
|
25
|
+
super("#{exception_class_name}: #{exception_message}")
|
26
|
+
self.backtrace = unexpected.exception.backtrace.map(&:to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
bt = Minitest.filter_backtrace(backtrace).join "\n "
|
31
|
+
"#{super}\n #{bt}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def result_label
|
35
|
+
"Error"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Override __run to use a child forks to run the speeds, which
|
40
|
+
# allows for parallel spec execution on MRI.
|
41
|
+
def self.__run(reporter, options)
|
42
|
+
suites = Runnable.runnables.shuffle
|
43
|
+
stat_reporter = reporter.reporters.detect{|rep| rep.is_a?(StatisticsReporter)}
|
44
|
+
|
45
|
+
n = (ENV['NCPU'] || 4).to_i
|
46
|
+
reads = []
|
47
|
+
if defined?(@before_parallel_fork)
|
48
|
+
@before_parallel_fork.call
|
49
|
+
end
|
50
|
+
n.times do |i|
|
51
|
+
read, write = IO.pipe.each{|io| io.binmode}
|
52
|
+
reads << read
|
53
|
+
fork do
|
54
|
+
read.close
|
55
|
+
if defined?(@after_parallel_fork)
|
56
|
+
@after_parallel_fork.call(i)
|
57
|
+
end
|
58
|
+
|
59
|
+
p_suites = []
|
60
|
+
suites.each_with_index{|s, j| p_suites << s if j % n == i}
|
61
|
+
p_suites.partition{|s| s.test_order != :parallel}.
|
62
|
+
map{|ss| ss.map{|s| s.run(reporter, options) }}
|
63
|
+
|
64
|
+
data = %w'count assertions results'.map{|meth| stat_reporter.send(meth)}
|
65
|
+
data[-1] = data[-1].map do |res|
|
66
|
+
[res.name, res.failures.map{|f| f.is_a?(UnexpectedError) ? DumpableUnexpectedError.new(f) : f}]
|
67
|
+
end
|
68
|
+
|
69
|
+
write.write(Marshal.dump(data))
|
70
|
+
write.close
|
71
|
+
end
|
72
|
+
write.close
|
73
|
+
end
|
74
|
+
Process.waitall
|
75
|
+
|
76
|
+
Thread.new do
|
77
|
+
reads.each do |r|
|
78
|
+
data = r.read
|
79
|
+
r.close
|
80
|
+
count, assertions, results = Marshal.load(data)
|
81
|
+
stat_reporter.count += count
|
82
|
+
stat_reporter.assertions += assertions
|
83
|
+
results.map! do |name, failures|
|
84
|
+
runnable = Test.new(name)
|
85
|
+
runnable.failures.concat(failures)
|
86
|
+
runnable
|
87
|
+
end
|
88
|
+
stat_reporter.results.concat(results)
|
89
|
+
end
|
90
|
+
end.join
|
91
|
+
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/parallel_fork'
|
3
|
+
|
4
|
+
a = nil
|
5
|
+
Minitest.before_parallel_fork do
|
6
|
+
a = 'a'
|
7
|
+
print ":parent"
|
8
|
+
end
|
9
|
+
|
10
|
+
Minitest.after_parallel_fork do |i|
|
11
|
+
print ":child#{i}#{a}"
|
12
|
+
end
|
13
|
+
|
14
|
+
4.times do |i|
|
15
|
+
describe 'minitest/parallel_fork' do
|
16
|
+
it "should work" do
|
17
|
+
sleep(1).must_equal 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
describe 'minitest/parallel_fork' do
|
4
|
+
it "should work" do
|
5
|
+
t = Time.now
|
6
|
+
ENV['NCPU'] = '4'
|
7
|
+
output = `#{ENV['RUBY']} -I lib spec/minitest_parallel_fork_example.rb`
|
8
|
+
(Time.now - t).must_be :<, 4
|
9
|
+
output.must_match /:parent/
|
10
|
+
4.times do |i|
|
11
|
+
output.must_match /:child#{i}a/
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: minitest-parallel_fork
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Evans
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
description: |
|
28
|
+
minitest-parallel_fork adds fork-based parallelization to Minitest. Each test/spec
|
29
|
+
suite is run one of the forks, allowing this to work correctly when using
|
30
|
+
before_all/after_all/around_all hooks provided by minitest-hooks. Using separate
|
31
|
+
processes via fork can significantly improve spec performance when using MRI,
|
32
|
+
and can work in cases where Minitest's default thread-based parallelism do not work,
|
33
|
+
such as when specs modify the constant namespace.
|
34
|
+
email: code@jeremyevans.net
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files:
|
38
|
+
- README.rdoc
|
39
|
+
- CHANGELOG
|
40
|
+
- MIT-LICENSE
|
41
|
+
files:
|
42
|
+
- CHANGELOG
|
43
|
+
- MIT-LICENSE
|
44
|
+
- README.rdoc
|
45
|
+
- Rakefile
|
46
|
+
- lib/minitest/parallel_fork.rb
|
47
|
+
- spec/minitest_parallel_fork_example.rb
|
48
|
+
- spec/minitest_parallel_fork_spec.rb
|
49
|
+
homepage: http://github.com/jeremyevans/minitest-parallel_fork
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- "--quiet"
|
56
|
+
- "--line-numbers"
|
57
|
+
- "--inline-source"
|
58
|
+
- "--title"
|
59
|
+
- 'minitest-parallel_fork: fork-based parallelization for minitest'
|
60
|
+
- "--main"
|
61
|
+
- README.rdoc
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.4.5
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Fork-based parallelization for minitest'
|
80
|
+
test_files: []
|