minitest-parallel_fork 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []