snap_ci-parallel_tests 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.init.sh +23 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +76 -0
- data/LICENSE.txt +22 -0
- data/README.md +60 -0
- data/Rakefile +7 -0
- data/bin/snap_ci-parallel_rspec +7 -0
- data/bin/snap_ci-parallel_test +7 -0
- data/lib/snap_ci/parallel_tests/cli.rb +130 -0
- data/lib/snap_ci/parallel_tests/grouper.rb +15 -0
- data/lib/snap_ci/parallel_tests/railtie.rb +9 -0
- data/lib/snap_ci/parallel_tests/rspec/cli_helper.rb +28 -0
- data/lib/snap_ci/parallel_tests/rspec/runner.rb +72 -0
- data/lib/snap_ci/parallel_tests/tasks/parallel_specs.rake +59 -0
- data/lib/snap_ci/parallel_tests/test/cli_helper.rb +28 -0
- data/lib/snap_ci/parallel_tests/test/runner.rb +53 -0
- data/lib/snap_ci/parallel_tests/version.rb +5 -0
- data/lib/snap_ci/parallel_tests.rb +71 -0
- data/snap_ci-parallel_tests.gemspec +23 -0
- data/spec/snap_ci/parallel_tests_spec.rb +43 -0
- data/spec/spec_helper.rb +2 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 64a3cfc18f6ec422dcec8eb046042c4cea62e645
|
4
|
+
data.tar.gz: e93d0f1bbd0c1318f185867dcf89c3f431983611
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d735091f3c891b157948e2642edbc69b1c57e23e36543299c2696d2a0ed4a85739d4e8dd084614a88c2bcb93a07c23fe40c6001249dac15e704d3c8971dccfbb
|
7
|
+
data.tar.gz: 4920b30f0b069989337624808180f995ce4d320f2ef5bbe4cc602d18dc107c97098f8d2fa22973eeb4726eb669222ffb974e0d82556bfc6874023ced4ffa84bc
|
data/.gitignore
ADDED
data/.init.sh
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
BUNDLE_PATH=/tmp/.bundle-$(basename $(pwd))
|
6
|
+
export BUNDLE_JOBS="${BUNDLE_JOBS:=4}"
|
7
|
+
if [ ! -z "$SNAP_CI" ]
|
8
|
+
then
|
9
|
+
BUNDLE_PATH=$HOME/.bundle
|
10
|
+
fi
|
11
|
+
|
12
|
+
mkdir -p $BUNDLE_PATH
|
13
|
+
rm -rf vendor/bundle
|
14
|
+
ln -sf $BUNDLE_PATH vendor/bundle
|
15
|
+
|
16
|
+
export NOKOGIRI_USE_SYSTEM_LIBRARIES=1
|
17
|
+
|
18
|
+
while read line; do
|
19
|
+
[[ -n ${SNAP_CI} || -n ${GO_SERVER_URL} ]] || echo -ne "Doing $((C++)) things...\r"
|
20
|
+
done < <(bundle check || bundle install --local --path vendor/bundle --clean)
|
21
|
+
|
22
|
+
echo
|
23
|
+
echo "Done!"
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
snap_ci-parallel_tests
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p598
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
snap_ci-parallel_tests (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
actionpack (4.1.8)
|
10
|
+
actionview (= 4.1.8)
|
11
|
+
activesupport (= 4.1.8)
|
12
|
+
rack (~> 1.5.2)
|
13
|
+
rack-test (~> 0.6.2)
|
14
|
+
actionview (4.1.8)
|
15
|
+
activesupport (= 4.1.8)
|
16
|
+
builder (~> 3.1)
|
17
|
+
erubis (~> 2.7.0)
|
18
|
+
activesupport (4.1.8)
|
19
|
+
i18n (~> 0.6, >= 0.6.9)
|
20
|
+
json (~> 1.7, >= 1.7.7)
|
21
|
+
minitest (~> 5.1)
|
22
|
+
thread_safe (~> 0.1)
|
23
|
+
tzinfo (~> 1.1)
|
24
|
+
builder (3.2.2)
|
25
|
+
diff-lcs (1.2.5)
|
26
|
+
erubis (2.7.0)
|
27
|
+
i18n (0.6.11)
|
28
|
+
json (1.8.1)
|
29
|
+
minitest (5.4.3)
|
30
|
+
power_assert (0.2.2)
|
31
|
+
rack (1.5.2)
|
32
|
+
rack-test (0.6.2)
|
33
|
+
rack (>= 1.0)
|
34
|
+
railties (4.1.8)
|
35
|
+
actionpack (= 4.1.8)
|
36
|
+
activesupport (= 4.1.8)
|
37
|
+
rake (>= 0.8.7)
|
38
|
+
thor (>= 0.18.1, < 2.0)
|
39
|
+
rake (10.4.2)
|
40
|
+
rspec (3.1.0)
|
41
|
+
rspec-core (~> 3.1.0)
|
42
|
+
rspec-expectations (~> 3.1.0)
|
43
|
+
rspec-mocks (~> 3.1.0)
|
44
|
+
rspec-core (3.1.7)
|
45
|
+
rspec-support (~> 3.1.0)
|
46
|
+
rspec-expectations (3.1.2)
|
47
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
+
rspec-support (~> 3.1.0)
|
49
|
+
rspec-mocks (3.1.3)
|
50
|
+
rspec-support (~> 3.1.0)
|
51
|
+
rspec-rails (3.1.0)
|
52
|
+
actionpack (>= 3.0)
|
53
|
+
activesupport (>= 3.0)
|
54
|
+
railties (>= 3.0)
|
55
|
+
rspec-core (~> 3.1.0)
|
56
|
+
rspec-expectations (~> 3.1.0)
|
57
|
+
rspec-mocks (~> 3.1.0)
|
58
|
+
rspec-support (~> 3.1.0)
|
59
|
+
rspec-support (3.1.2)
|
60
|
+
test-unit (3.0.7)
|
61
|
+
power_assert
|
62
|
+
thor (0.19.1)
|
63
|
+
thread_safe (0.3.4)
|
64
|
+
tzinfo (1.2.2)
|
65
|
+
thread_safe (~> 0.1)
|
66
|
+
|
67
|
+
PLATFORMS
|
68
|
+
ruby
|
69
|
+
|
70
|
+
DEPENDENCIES
|
71
|
+
bundler (~> 1.7)
|
72
|
+
rake
|
73
|
+
rspec (>= 2.12.0)
|
74
|
+
rspec-rails (>= 2.12.0)
|
75
|
+
snap_ci-parallel_tests!
|
76
|
+
test-unit
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 ThoughtWorks, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# SnapCI::ParallelTests
|
2
|
+
|
3
|
+
Run Test::Unit / RSpec in parallel across multiple workers on [Snap CI](https://snap-ci.com).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'snap_ci-parallel_tests'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install snap_ci-parallel_tests
|
20
|
+
|
21
|
+
## Setup for non-rails
|
22
|
+
|
23
|
+
Depending on the framework of your choice -
|
24
|
+
|
25
|
+
$ bundle exec snap_ci-parallel_rspec [options] [files or directories] [-- [rspec options]]
|
26
|
+
$ bundle exec snap_ci-parallel_test [options] [files or directories] [-- [Test::Unit or MiniTest options]]
|
27
|
+
|
28
|
+
|
29
|
+
## Setup for Rails
|
30
|
+
|
31
|
+
Ensure that 'parallel_tests' is present in your development group
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# Gemfile
|
35
|
+
gem "parallel_tests", :group => :development
|
36
|
+
```
|
37
|
+
|
38
|
+
### Run
|
39
|
+
|
40
|
+
$ bundle exec snap_ci-parallel_rspec [options] [files or directories] [-- [rspec options]]
|
41
|
+
$ bundle exec snap_ci-parallel_test [options] [files or directories] [-- [Test::Unit or MiniTest options]]
|
42
|
+
|
43
|
+
Alternatively -
|
44
|
+
|
45
|
+
```shell
|
46
|
+
$ bundle exec rake snap-parallel # to run all specs
|
47
|
+
$ bundle exec rake snap-parallel:models # to run only model specs
|
48
|
+
$ bundle exec rake snap-parallel:controllers # to run only controllers specs
|
49
|
+
$ bundle exec rake -T snap-parallel # to list all tasks
|
50
|
+
```
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
1. Fork it ( https://github.com/[my-github-username]/snap_ci-parallel_tests/fork )
|
57
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
58
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
59
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
60
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
3
|
+
require 'snap_ci/parallel_tests'
|
4
|
+
require 'snap_ci/parallel_tests/cli'
|
5
|
+
require 'snap_ci/parallel_tests/rspec/runner'
|
6
|
+
|
7
|
+
SnapCI::ParallelTests::CLI.new(SnapCI::ParallelTests::RSpec::Runner.new, ARGV).run
|
@@ -0,0 +1,7 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
3
|
+
require 'snap_ci/parallel_tests'
|
4
|
+
require 'snap_ci/parallel_tests/cli'
|
5
|
+
require 'snap_ci/parallel_tests/test/runner'
|
6
|
+
|
7
|
+
SnapCI::ParallelTests::CLI.new(SnapCI::ParallelTests::Test::Runner.new, ARGV).run
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module SnapCI
|
4
|
+
module ParallelTests
|
5
|
+
class CLI
|
6
|
+
attr_reader :runner, :argv, :test_opts
|
7
|
+
|
8
|
+
def initialize(runner, argv)
|
9
|
+
@runner = runner
|
10
|
+
|
11
|
+
@argv = argv.dup
|
12
|
+
@test_opts = nil
|
13
|
+
|
14
|
+
if split_index = @argv.index('--')
|
15
|
+
@test_opts = @argv.drop(split_index + 1).join(' ')
|
16
|
+
@argv = @argv.take(split_index)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
options = parse!
|
22
|
+
test_files = find_all_tests(options[:files_or_dirs], options)
|
23
|
+
|
24
|
+
if options[:trace]
|
25
|
+
$stderr.puts "Found the following #{runner.test_file_name}s"
|
26
|
+
$stderr.puts test_files.collect { |t| " - #{t}" }.join("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
report_number_of_tests(test_files, SnapCI::ParallelTests.total_workers)
|
30
|
+
|
31
|
+
test_files_to_run = SnapCI::ParallelTests.partition(
|
32
|
+
things: test_files,
|
33
|
+
total_workers: SnapCI::ParallelTests.total_workers,
|
34
|
+
current_worker_index: SnapCI::ParallelTests.worker_index,
|
35
|
+
group_by: options[:group_by]
|
36
|
+
)
|
37
|
+
|
38
|
+
if test_files_to_run.empty?
|
39
|
+
$stderr.puts 'No tests to run'
|
40
|
+
else
|
41
|
+
if options[:trace]
|
42
|
+
$stderr.puts "Will run the following #{runner.test_file_name}s and ignore others"
|
43
|
+
$stderr.puts test_files.collect { |t| " - #{t}" }.join("\n")
|
44
|
+
end
|
45
|
+
runner.execute(test_files_to_run, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def find_all_tests(tests, options = {})
|
54
|
+
(tests || []).map do |file_or_folder|
|
55
|
+
if File.directory?(file_or_folder)
|
56
|
+
files = files_in_folder(file_or_folder, options)
|
57
|
+
files.grep(runner.test_suffix).grep(options[:pattern]||//)
|
58
|
+
else
|
59
|
+
file_or_folder
|
60
|
+
end
|
61
|
+
end.flatten.uniq
|
62
|
+
end
|
63
|
+
|
64
|
+
def files_in_folder(folder, options={})
|
65
|
+
pattern = if options[:symlinks] == false # not nil or true
|
66
|
+
'**/*'
|
67
|
+
else
|
68
|
+
# follow one symlink and direct children
|
69
|
+
# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
|
70
|
+
'**{,/*/**}/*'
|
71
|
+
end
|
72
|
+
Dir[File.join(folder, pattern)].uniq
|
73
|
+
end
|
74
|
+
|
75
|
+
def report_number_of_tests(tests, total_workers)
|
76
|
+
name = runner.test_file_name
|
77
|
+
num_tests = tests.size
|
78
|
+
$stderr.puts "#{total_workers} workers for #{num_tests} #{name}s, ~ #{(num_tests.to_f/total_workers).ceil} #{name}s per process"
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse!
|
82
|
+
options = {}
|
83
|
+
|
84
|
+
options[:group_by] = :filename
|
85
|
+
|
86
|
+
parser = OptionParser.new do |opts|
|
87
|
+
runner.cli_helper.render_header(opts)
|
88
|
+
opts.separator 'supported options:'
|
89
|
+
|
90
|
+
runner.cli_helper.render_options(opts)
|
91
|
+
|
92
|
+
opts.on('-g', '--group-by TYPE', <<-TEXT) do |type|
|
93
|
+
group tests by:
|
94
|
+
filename - order of finding files(default)
|
95
|
+
filesize - by size of the file
|
96
|
+
TEXT
|
97
|
+
raise unless %w(name filesize).include?(type)
|
98
|
+
options[:group_by] = type.to_sym
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.on('-v', '--version', 'Show Version') do
|
102
|
+
puts "SnapCI Parallel Tests v#{SnapCI::ParallelTests::VERSION}"
|
103
|
+
exit
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on('-t', '--trace', 'Turn on verbose mode') do |trace|
|
107
|
+
options[:trace] = trace
|
108
|
+
end
|
109
|
+
|
110
|
+
opts.on('-h', '--help', 'Show this help screen.') do
|
111
|
+
puts opts
|
112
|
+
exit
|
113
|
+
end
|
114
|
+
|
115
|
+
runner.cli_helper.render_footer(opts)
|
116
|
+
end
|
117
|
+
|
118
|
+
parser.parse!(argv)
|
119
|
+
|
120
|
+
options[:files_or_dirs] = argv
|
121
|
+
options[:test_opts] = test_opts
|
122
|
+
|
123
|
+
if options[:trace]
|
124
|
+
$stderr.puts "trace - got options #{options.inspect}"
|
125
|
+
end
|
126
|
+
options
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SnapCI
|
2
|
+
module ParallelTests
|
3
|
+
module RSpec
|
4
|
+
module CLIHelper
|
5
|
+
def render_header(opts)
|
6
|
+
opts.banner = <<BANNER
|
7
|
+
Usage: #{opts.program_name} [options] [files or directories] [-- [rspec options]]
|
8
|
+
|
9
|
+
Example: #{opts.program_name} spec/models spec/controllers/foo_controller_spec.rb -- --format documentation --fail-fast
|
10
|
+
BANNER
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_options(opts)
|
14
|
+
opts.on('-p', '--pattern [PATTERN]', 'run tests matching this pattern') do |pattern|
|
15
|
+
options[:pattern] = /#{pattern}/
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_footer(opts)
|
20
|
+
opts.separator "\nRun `rspec --help' for supported rspec options."
|
21
|
+
end
|
22
|
+
|
23
|
+
extend CLIHelper
|
24
|
+
end #CLIHelper
|
25
|
+
|
26
|
+
end #RSpec
|
27
|
+
end #ParallelTests
|
28
|
+
end #SnapCI
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'snap_ci/parallel_tests/rspec/cli_helper'
|
3
|
+
|
4
|
+
module SnapCI
|
5
|
+
module ParallelTests
|
6
|
+
module RSpec
|
7
|
+
class Runner
|
8
|
+
|
9
|
+
def execute(test_files, options)
|
10
|
+
exe = determine_executable
|
11
|
+
version = (exe =~ /\brspec\b/ ? 2 : 1)
|
12
|
+
|
13
|
+
test_files = test_files.map { |f| Shellwords.escape(f) }
|
14
|
+
|
15
|
+
cmd = [exe, options[:test_opts], (rspec_2_color if version == 2), *test_files].compact.join(' ')
|
16
|
+
options = options.merge(env: rspec_1_color) #if version == 1
|
17
|
+
|
18
|
+
$stderr.puts(cmd)
|
19
|
+
exec((options[:env] || {}), cmd)
|
20
|
+
end
|
21
|
+
|
22
|
+
def cli_helper
|
23
|
+
CLIHelper
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_suffix
|
27
|
+
/_spec\.rb$/
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_file_name
|
31
|
+
'spec'
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def determine_executable
|
37
|
+
cmd = case
|
38
|
+
when File.exists?('bin/rspec')
|
39
|
+
WINDOWS ? 'ruby bin/rspec' : 'bin/rspec'
|
40
|
+
when File.file?('script/spec')
|
41
|
+
'script/spec'
|
42
|
+
when SnapCI::ParallelTests.bundler_enabled?
|
43
|
+
cmd = (output_of('bundle show rspec-core') =~ %r{Could not find gem.*} ? 'spec' : 'rspec')
|
44
|
+
"bundle exec #{cmd}"
|
45
|
+
else
|
46
|
+
%w[spec rspec].detect { |cmd| system "#{cmd} --version > #{DEV_NULL} 2>&1" }
|
47
|
+
end
|
48
|
+
|
49
|
+
cmd or raise("Can't find executables rspec or spec")
|
50
|
+
end
|
51
|
+
|
52
|
+
def output_of(cmd)
|
53
|
+
`#{cmd}`
|
54
|
+
end
|
55
|
+
|
56
|
+
def rspec_2_color
|
57
|
+
'--color --tty' if $stdout.tty?
|
58
|
+
end
|
59
|
+
|
60
|
+
def rspec_1_color
|
61
|
+
if $stdout.tty?
|
62
|
+
{ 'RSPEC_COLOR' => '1' }
|
63
|
+
else
|
64
|
+
{}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end #Runner
|
69
|
+
|
70
|
+
end #RSpec
|
71
|
+
end #ParallelTests
|
72
|
+
end #SnapCI
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#try and see what frameworks are available
|
2
|
+
|
3
|
+
%w(
|
4
|
+
rspec-rails
|
5
|
+
rspec/core
|
6
|
+
rspec/core/rake_task
|
7
|
+
).each do |library|
|
8
|
+
begin
|
9
|
+
require library
|
10
|
+
rescue LoadError
|
11
|
+
puts "could not require #{library}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
num_workers = SnapCI::ParallelTests.total_workers
|
16
|
+
worker_index = SnapCI::ParallelTests.worker_index
|
17
|
+
|
18
|
+
desc 'Run all specs in spec directory (excluding plugin specs)'
|
19
|
+
RSpec::Core::RakeTask.new(:'snap-parallel' => 'snap-parallel:prepare') do |t|
|
20
|
+
all_specs = FileList['./spec/**{,/*/**}/*_spec.rb'].sort
|
21
|
+
specs_to_run = SnapCI::ParallelTests.partition(things: all_specs, total_workers: num_workers, current_worker_index: worker_index)
|
22
|
+
|
23
|
+
if specs_to_run && specs_to_run.count > 0
|
24
|
+
t.pattern = specs_to_run
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
namespace :'snap-parallel' do
|
29
|
+
types = begin
|
30
|
+
dirs = Dir['./spec/**/*_spec.rb'].
|
31
|
+
map { |f| f.sub(/^\.\/(spec\/\w+)\/.*/, '\\1') }.
|
32
|
+
uniq.
|
33
|
+
select { |f| File.directory?(f) }
|
34
|
+
Hash[dirs.map { |d| [d.split('/').last, d] }]
|
35
|
+
end
|
36
|
+
|
37
|
+
task :prepare do
|
38
|
+
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = 'test'
|
39
|
+
if Rails.configuration.generators.options[:rails][:orm] == :active_record
|
40
|
+
if Rake::Task.task_defined?('test:prepare')
|
41
|
+
Rake::Task['test:prepare'].invoke
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
types.each do |type, dir|
|
47
|
+
desc "Run the code examples in #{dir}"
|
48
|
+
all_specs = FileList["./#{dir}/**/*_spec.rb"].sort
|
49
|
+
|
50
|
+
RSpec::Core::RakeTask.new(type => 'snap-parallel:prepare') do |t|
|
51
|
+
specs_to_run = SnapCI::ParallelTests.partition(things: all_specs, total_workers: num_workers, current_worker_index: worker_index)
|
52
|
+
|
53
|
+
if specs_to_run && specs_to_run.count > 0
|
54
|
+
t.pattern = specs_to_run
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SnapCI
|
2
|
+
module ParallelTests
|
3
|
+
module Test
|
4
|
+
module CLIHelper
|
5
|
+
def render_header(opts)
|
6
|
+
opts.banner = <<BANNER
|
7
|
+
Usage: #{opts.program_name} [options] [files or directories] [-- [Test::Unit or MiniTest options]]
|
8
|
+
|
9
|
+
Example: #{opts.program_name} test/models test/controllers/foo_controller_test.rb -- --verbose --seed 10
|
10
|
+
BANNER
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_options(opts)
|
14
|
+
opts.on('-p', '--pattern [PATTERN]', 'run tests matching this pattern') do |pattern|
|
15
|
+
options[:pattern] = /#{pattern}/
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_footer(opts)
|
20
|
+
opts.separator "\nRun `ruby -r test/test_helper -e1 --help' for supported Test::Unit or MiniTest options."
|
21
|
+
end
|
22
|
+
|
23
|
+
extend CLIHelper
|
24
|
+
end #CLIHelper
|
25
|
+
|
26
|
+
end #RSpec
|
27
|
+
end #ParallelTests
|
28
|
+
end #SnapCI
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'snap_ci/parallel_tests/test/cli_helper'
|
3
|
+
|
4
|
+
module SnapCI
|
5
|
+
module ParallelTests
|
6
|
+
module Test
|
7
|
+
class Runner
|
8
|
+
|
9
|
+
def execute(test_files, options)
|
10
|
+
test_files = test_files.map { |f| Shellwords.escape(f) }
|
11
|
+
|
12
|
+
cmd = ['ruby', '-Itest', test_files, options[:test_opts]].flatten.compact.join(' ')
|
13
|
+
|
14
|
+
$stderr.puts(cmd)
|
15
|
+
|
16
|
+
exec(cmd)
|
17
|
+
end
|
18
|
+
|
19
|
+
def cli_helper
|
20
|
+
CLIHelper
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_suffix
|
24
|
+
/_(test|spec).rb$/
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_file_name
|
28
|
+
'test'
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def output_of(cmd)
|
34
|
+
`#{cmd}`
|
35
|
+
end
|
36
|
+
|
37
|
+
def rspec_2_color
|
38
|
+
'--color --tty' if $stdout.tty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def rspec_1_color
|
42
|
+
if $stdout.tty?
|
43
|
+
{ 'RSPEC_COLOR' => '1' }
|
44
|
+
else
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end #Runner
|
50
|
+
|
51
|
+
end #RSpec
|
52
|
+
end #ParallelTests
|
53
|
+
end #SnapCI
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'snap_ci/parallel_tests/version'
|
2
|
+
require 'snap_ci/parallel_tests/grouper'
|
3
|
+
require 'snap_ci/parallel_tests/railtie' if defined?(Rails::Railtie)
|
4
|
+
|
5
|
+
module SnapCI
|
6
|
+
module ParallelTests
|
7
|
+
WINDOWS = (RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/)
|
8
|
+
|
9
|
+
# Partitions the a bunch of things to be run on multiple workers, it partitions based on the following options
|
10
|
+
#
|
11
|
+
# ==== Options
|
12
|
+
# +things+ - the things to partition
|
13
|
+
# +total_workers+ - the total number of workers, defaults to ParallelTests.total_workers
|
14
|
+
# +current_worker_index+ - the current worker index (1 based, NOT 0 based), defaults to ParallelTests.worker_index
|
15
|
+
# +group_by+ - either :filename or :filesize (defaults to :filename). Determines how files are sorted before being partitioned
|
16
|
+
def partition(options={})
|
17
|
+
things = options[:things]
|
18
|
+
total_workers = options[:total_workers] || ParallelTests.total_workers
|
19
|
+
current_worker_index = options[:current_worker_index] || ParallelTests.worker_index
|
20
|
+
group_by = options[:group_by] || :filename
|
21
|
+
|
22
|
+
return [] if things.nil? || things.empty?
|
23
|
+
things = Grouper.send("group_by_#{group_by}", things)
|
24
|
+
|
25
|
+
result = []
|
26
|
+
|
27
|
+
# pick up things on a round-robin basis to distribute them evenly
|
28
|
+
index = current_worker_index - 1
|
29
|
+
while index <= things.count do
|
30
|
+
result << things[index]
|
31
|
+
index += total_workers
|
32
|
+
end
|
33
|
+
result.compact!
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def total_workers
|
39
|
+
if ENV['SNAP_WORKER_TOTAL']
|
40
|
+
ENV['SNAP_WORKER_TOTAL'].to_i
|
41
|
+
else
|
42
|
+
1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def worker_index
|
47
|
+
if ENV['SNAP_WORKER_INDEX']
|
48
|
+
ENV['SNAP_WORKER_INDEX'].to_i
|
49
|
+
else
|
50
|
+
1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def bundler_enabled?
|
55
|
+
return true if Object.const_defined?(:Bundler)
|
56
|
+
|
57
|
+
previous = nil
|
58
|
+
current = File.expand_path(Dir.pwd)
|
59
|
+
|
60
|
+
until !File.directory?(current) || current == previous
|
61
|
+
filename = File.join(current, 'Gemfile')
|
62
|
+
return true if File.exists?(filename)
|
63
|
+
current, previous = File.expand_path('..', current), current
|
64
|
+
end
|
65
|
+
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
extend SnapCI::ParallelTests
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'snap_ci/parallel_tests/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "snap_ci-parallel_tests"
|
8
|
+
spec.version = SnapCI::ParallelTests::VERSION
|
9
|
+
spec.authors = ["Ketan Padegaonkar"]
|
10
|
+
spec.email = ["KetanPadegaonkar@gmail.com"]
|
11
|
+
spec.summary = %q{Run Test::Unit / RSpec in parallel across multiple workers on Snap CI}
|
12
|
+
spec.description = %q{Run Test::Unit / RSpec in parallel across multiple workers on Snap CI}
|
13
|
+
spec.homepage = "https://github.com/snap-ci/parallel-tests"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SnapCI::ParallelTests do
|
4
|
+
include SnapCI::ParallelTests
|
5
|
+
|
6
|
+
it 'should create uniform partitions of things based on count and index' do
|
7
|
+
things = (1..10).to_a
|
8
|
+
|
9
|
+
partition1 = partition(things: things, total_workers: 3, current_worker_index: 1)
|
10
|
+
partition2 = partition(things: things, total_workers: 3, current_worker_index: 2)
|
11
|
+
partition3 = partition(things: things, total_workers: 3, current_worker_index: 3)
|
12
|
+
|
13
|
+
expect(partition1 + partition2 + partition3).to contain_exactly(*things)
|
14
|
+
|
15
|
+
expect(partition1).to eq([1, 4, 7, 10])
|
16
|
+
expect(partition2).to eq([2, 5, 8])
|
17
|
+
expect(partition3).to eq([3, 6, 9])
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should create empty partitions if things are empty' do
|
21
|
+
things = []
|
22
|
+
|
23
|
+
partition1 = partition(things: things, total_workers: 3, current_worker_index: 1)
|
24
|
+
partition2 = partition(things: things, total_workers: 3, current_worker_index: 2)
|
25
|
+
partition3 = partition(things: things, total_workers: 3, current_worker_index: 3)
|
26
|
+
|
27
|
+
expect(partition1).to eq([])
|
28
|
+
expect(partition2).to eq([])
|
29
|
+
expect(partition3).to eq([])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should create empty partitions at end if partitions are more than number of things' do
|
33
|
+
things = [1, 2]
|
34
|
+
|
35
|
+
partition1 = partition(things: things, total_workers: 3, current_worker_index: 1)
|
36
|
+
partition2 = partition(things: things, total_workers: 3, current_worker_index: 2)
|
37
|
+
partition3 = partition(things: things, total_workers: 3, current_worker_index: 3)
|
38
|
+
|
39
|
+
expect(partition1).to eq([1])
|
40
|
+
expect(partition2).to eq([2])
|
41
|
+
expect(partition3).to eq([])
|
42
|
+
end
|
43
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: snap_ci-parallel_tests
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ketan Padegaonkar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Run Test::Unit / RSpec in parallel across multiple workers on Snap CI
|
42
|
+
email:
|
43
|
+
- KetanPadegaonkar@gmail.com
|
44
|
+
executables:
|
45
|
+
- snap_ci-parallel_rspec
|
46
|
+
- snap_ci-parallel_test
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- .gitignore
|
51
|
+
- .init.sh
|
52
|
+
- .ruby-gemset
|
53
|
+
- .ruby-version
|
54
|
+
- Gemfile
|
55
|
+
- Gemfile.lock
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- bin/snap_ci-parallel_rspec
|
60
|
+
- bin/snap_ci-parallel_test
|
61
|
+
- lib/snap_ci/parallel_tests.rb
|
62
|
+
- lib/snap_ci/parallel_tests/cli.rb
|
63
|
+
- lib/snap_ci/parallel_tests/grouper.rb
|
64
|
+
- lib/snap_ci/parallel_tests/railtie.rb
|
65
|
+
- lib/snap_ci/parallel_tests/rspec/cli_helper.rb
|
66
|
+
- lib/snap_ci/parallel_tests/rspec/runner.rb
|
67
|
+
- lib/snap_ci/parallel_tests/tasks/parallel_specs.rake
|
68
|
+
- lib/snap_ci/parallel_tests/test/cli_helper.rb
|
69
|
+
- lib/snap_ci/parallel_tests/test/runner.rb
|
70
|
+
- lib/snap_ci/parallel_tests/version.rb
|
71
|
+
- snap_ci-parallel_tests.gemspec
|
72
|
+
- spec/snap_ci/parallel_tests_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
homepage: https://github.com/snap-ci/parallel-tests
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.4.4
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Run Test::Unit / RSpec in parallel across multiple workers on Snap CI
|
98
|
+
test_files:
|
99
|
+
- spec/snap_ci/parallel_tests_spec.rb
|
100
|
+
- spec/spec_helper.rb
|