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 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
@@ -0,0 +1,3 @@
1
+ === 1.0.0 (2015-05-11)
2
+
3
+ * Initial Public Release
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: []