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 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.bundle
10
+ *.so
11
+ *.o
12
+ *.a
13
+ mkmf.log
14
+ vendor/bundle
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
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in snap_ci-parallel_tests.gemspec
4
+ gemspec
5
+
6
+ gem 'test-unit'
7
+ gem 'rspec', '>=2.12.0'
8
+ gem 'rspec-rails', '>=2.12.0'
9
+ gem 'rake'
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
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -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,15 @@
1
+ module SnapCI
2
+ module ParallelTests
3
+ class Grouper
4
+ class <<self
5
+ def group_by_filename(things)
6
+ things.sort
7
+ end
8
+
9
+ def group_by_filesize(things)
10
+ things.sort { |a, b| File.size(a) <=> File.size(b) }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module SnapCI
2
+ module ParallelTests
3
+ class Railtie < Rails::Railtie
4
+ rake_tasks do
5
+ load 'snap_ci/parallel_tests/tasks/parallel_specs.rake'
6
+ end
7
+ end
8
+ end
9
+ 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,5 @@
1
+ module SnapCI
2
+ module ParallelTests
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -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
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'snap_ci/parallel_tests'
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