snap_ci-parallel_tests 0.0.1
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/.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
|