friendlyfashion-parallel_tests 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +8 -0
  3. data/Gemfile.lock +44 -0
  4. data/Rakefile +6 -0
  5. data/Readme.md +232 -0
  6. data/ReadmeRails2.md +48 -0
  7. data/bin/parallel_cucumber +2 -0
  8. data/bin/parallel_rspec +2 -0
  9. data/bin/parallel_test +6 -0
  10. data/lib/parallel_tests.rb +30 -0
  11. data/lib/parallel_tests/cli.rb +159 -0
  12. data/lib/parallel_tests/cucumber/gherkin_listener.rb +60 -0
  13. data/lib/parallel_tests/cucumber/runner.rb +90 -0
  14. data/lib/parallel_tests/cucumber/runtime_logger.rb +58 -0
  15. data/lib/parallel_tests/grouper.rb +53 -0
  16. data/lib/parallel_tests/railtie.rb +8 -0
  17. data/lib/parallel_tests/rspec/failures_logger.rb +44 -0
  18. data/lib/parallel_tests/rspec/logger_base.rb +52 -0
  19. data/lib/parallel_tests/rspec/runner.rb +59 -0
  20. data/lib/parallel_tests/rspec/runtime_logger.rb +34 -0
  21. data/lib/parallel_tests/rspec/summary_logger.rb +19 -0
  22. data/lib/parallel_tests/tasks.rb +134 -0
  23. data/lib/parallel_tests/test/runner.rb +134 -0
  24. data/lib/parallel_tests/test/runtime_logger.rb +92 -0
  25. data/lib/parallel_tests/version.rb +3 -0
  26. data/parallel_tests.gemspec +14 -0
  27. data/spec/integration_spec.rb +244 -0
  28. data/spec/parallel_tests/cli_spec.rb +36 -0
  29. data/spec/parallel_tests/cucumber/gherkin_listener_spec.rb +48 -0
  30. data/spec/parallel_tests/cucumber/runner_spec.rb +173 -0
  31. data/spec/parallel_tests/grouper_spec.rb +52 -0
  32. data/spec/parallel_tests/rspec/failure_logger_spec.rb +82 -0
  33. data/spec/parallel_tests/rspec/runner_spec.rb +178 -0
  34. data/spec/parallel_tests/rspec/runtime_logger_spec.rb +76 -0
  35. data/spec/parallel_tests/rspec/summary_logger_spec.rb +37 -0
  36. data/spec/parallel_tests/tasks_spec.rb +151 -0
  37. data/spec/parallel_tests/test/runner_spec.rb +273 -0
  38. data/spec/parallel_tests/test/runtime_logger_spec.rb +84 -0
  39. data/spec/parallel_tests_spec.rb +73 -0
  40. data/spec/spec_helper.rb +151 -0
  41. metadata +109 -0
@@ -0,0 +1,19 @@
1
+ require 'parallel_tests/rspec/failures_logger'
2
+
3
+ class ParallelTests::RSpec::SummaryLogger < ParallelTests::RSpec::LoggerBase
4
+ # RSpec 1: dumps 1 failed spec
5
+ def dump_failure(*args)
6
+ lock_output do
7
+ super
8
+ end
9
+ @output.flush
10
+ end
11
+
12
+ # RSpec 2: dumps all failed specs
13
+ def dump_failures(*args)
14
+ lock_output do
15
+ super
16
+ end
17
+ @output.flush
18
+ end
19
+ end
@@ -0,0 +1,134 @@
1
+ require 'rake'
2
+
3
+ module ParallelTests
4
+ module Tasks
5
+ class << self
6
+ def rails_env
7
+ ENV['RAILS_ENV'] || 'test'
8
+ end
9
+
10
+ def run_in_parallel(cmd, options={})
11
+ count = " -n #{options[:count]}" if options[:count]
12
+ executable = File.expand_path("../../../bin/parallel_test", __FILE__)
13
+ command = "#{executable} --exec '#{cmd}'#{count}#{' --non-parallel' if options[:non_parallel]}"
14
+ command << " --advance-number #{options[:advance_number]}" if options[:advance_number]
15
+ abort unless system(command)
16
+ end
17
+
18
+ # this is a crazy-complex solution for a very simple problem:
19
+ # removing certain lines from the output without chaning the exit-status
20
+ # normally I'd not do this, but it has been lots of fun and a great learning experience :)
21
+ #
22
+ # - sed does not support | without -r
23
+ # - grep changes 0 exitstatus to 1 if nothing matches
24
+ # - sed changes 1 exitstatus to 0
25
+ # - pipefail makes pipe fail with exitstatus of first failed command
26
+ # - pipefail is not supported in (zsh)
27
+ # - defining a new rake task like silence_schema would force users to load parallel_tests in test env
28
+ # - do not use ' since run_in_parallel uses them to quote stuff
29
+ # - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
30
+ def suppress_output(command, ignore_regex)
31
+ activate_pipefail = "set -o pipefail"
32
+ remove_ignored_lines = %Q{(grep -v "#{ignore_regex}" || test 1)}
33
+
34
+ if system("#{activate_pipefail} && test 1")
35
+ "#{activate_pipefail} && (#{command}) | #{remove_ignored_lines}"
36
+ else
37
+ command
38
+ end
39
+ end
40
+
41
+ def check_for_pending_migrations
42
+ abort_migrations = "db:abort_if_pending_migrations"
43
+ if Rake::Task.task_defined?(abort_migrations)
44
+ Rake::Task[abort_migrations].invoke
45
+ end
46
+ end
47
+
48
+ # parallel:spec[:count, :pattern, :options]
49
+ def parse_args(args)
50
+ # order as given by user
51
+ args = [args[:count], args[:pattern], args[:options]]
52
+
53
+ # count given or empty ?
54
+ # parallel:spec[2,models,options]
55
+ # parallel:spec[,models,options]
56
+ count = args.shift if args.first.to_s =~ /^\d*$/
57
+ num_processes = count.to_i unless count.to_s.empty?
58
+ pattern = args.shift
59
+ options = args.shift
60
+
61
+ [num_processes, pattern.to_s, options.to_s]
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ namespace :parallel do
68
+ desc "create test databases via db:create --> parallel:create[num_cpus]"
69
+ task :create, :count do |t,args|
70
+ ParallelTests::Tasks.run_in_parallel("rake db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
71
+ end
72
+
73
+ desc "drop test databases via db:drop --> parallel:drop[num_cpus]"
74
+ task :drop, :count do |t,args|
75
+ ParallelTests::Tasks.run_in_parallel("rake db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
76
+ end
77
+
78
+ desc "update test databases by dumping and loading --> parallel:prepare[num_cpus]"
79
+ task(:prepare, [:count]) do |t,args|
80
+ ParallelTests::Tasks.check_for_pending_migrations
81
+ if defined?(ActiveRecord) && ActiveRecord::Base.schema_format == :ruby
82
+ # dump then load in parallel
83
+ Rake::Task['db:schema:dump'].invoke
84
+ Rake::Task['parallel:load_schema'].invoke(args[:count])
85
+ else
86
+ # there is no separate dump / load for schema_format :sql -> do it safe and slow
87
+ args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
88
+ ParallelTests::Tasks.run_in_parallel('rake db:test:prepare --trace', args)
89
+ end
90
+ end
91
+
92
+ # when dumping/resetting takes too long
93
+ desc "update test databases via db:migrate --> parallel:migrate[num_cpus]"
94
+ task :migrate, :count do |t,args|
95
+ ParallelTests::Tasks.run_in_parallel("rake db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
96
+ end
97
+
98
+ # just load the schema (good for integration server <-> no development db)
99
+ desc "load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
100
+ task :load_schema, :count do |t,args|
101
+ command = "rake db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
102
+ ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_output(command, "^ ->\\|^-- "), args)
103
+ end
104
+
105
+ desc "load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
106
+ task :seed, :count do |t,args|
107
+ ParallelTests::Tasks.run_in_parallel("rake db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
108
+ end
109
+
110
+ ['test', 'spec', 'features'].each do |type|
111
+ desc "run #{type} in parallel with parallel:#{type}[num_cpus]"
112
+ task type, [:count, :pattern, :options] do |t, args|
113
+ ParallelTests::Tasks.check_for_pending_migrations
114
+
115
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
116
+ require "parallel_tests"
117
+
118
+ count, pattern, options = ParallelTests::Tasks.parse_args(args)
119
+ test_framework = {
120
+ 'spec' => 'rspec',
121
+ 'test' => 'test',
122
+ 'features' => 'cucumber'
123
+ }[type]
124
+
125
+ executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
126
+ command = "#{executable} #{type} --type #{test_framework} " \
127
+ "-n #{count} " \
128
+ "--pattern '#{pattern}' " \
129
+ "--test-options '#{options}'"
130
+
131
+ abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,134 @@
1
+ module ParallelTests
2
+ module Test
3
+ class Runner
4
+ # --- usually overwritten by other runners
5
+
6
+ def self.runtime_log
7
+ 'tmp/parallel_runtime_test.log'
8
+ end
9
+
10
+ def self.test_suffix
11
+ "_test.rb"
12
+ end
13
+
14
+ def self.test_file_name
15
+ "test"
16
+ end
17
+
18
+ def self.run_tests(test_files, process_number, options)
19
+ require_list = test_files.map { |filename| %{"#{File.expand_path filename}"} }.join(",")
20
+ cmd = "ruby -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"
21
+ execute_command(cmd, process_number, options)
22
+ end
23
+
24
+ def self.line_is_result?(line)
25
+ line =~ /\d+ failure/
26
+ end
27
+
28
+ # --- usually used by other runners
29
+
30
+ # finds all tests and partitions them into groups
31
+ def self.tests_in_groups(tests, num_groups, options={})
32
+ tests = find_tests(tests, options)
33
+
34
+ tests = if options[:group_by] == :found
35
+ tests.map { |t| [t, 1] }
36
+ else
37
+ with_runtime_info(tests)
38
+ end
39
+ Grouper.in_even_groups_by_size(tests, num_groups, options)
40
+ end
41
+
42
+ def self.execute_command(cmd, process_number, options)
43
+ cmd = "TEST_ENV_NUMBER=#{test_env_number(process_number, options)} ; export TEST_ENV_NUMBER; #{cmd}"
44
+ f = open("|#{cmd}", 'r')
45
+ output = fetch_output(f)
46
+ f.close
47
+ {:stdout => output, :exit_status => $?.exitstatus}
48
+ end
49
+
50
+ def self.find_results(test_output)
51
+ test_output.split("\n").map {|line|
52
+ line = line.gsub(/\.|F|\*/,'').gsub(/\e\[\d+m/,'')
53
+ next unless line_is_result?(line)
54
+ line
55
+ }.compact
56
+ end
57
+
58
+ def self.test_env_number(process_number, options)
59
+ n = options[:advance_number].to_i + process_number + 1
60
+ n == 0 ? '' : n
61
+ end
62
+
63
+ def self.summarize_results(results)
64
+ sums = sum_up_results(results)
65
+ sums.sort.map{|word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
66
+ end
67
+
68
+ protected
69
+
70
+ def self.sum_up_results(results)
71
+ results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
72
+ counts = results.scan(/(\d+) (\w+)/)
73
+ sums = counts.inject(Hash.new(0)) do |sum, (number, word)|
74
+ sum[word] += number.to_i
75
+ sum
76
+ end
77
+
78
+ sums
79
+ end
80
+
81
+ # read output of the process and print it in chunks
82
+ def self.fetch_output(process)
83
+ all = ''
84
+ while buffer = process.readpartial(1000000)
85
+ all << buffer
86
+ $stdout.print buffer
87
+ $stdout.flush
88
+ end rescue EOFError
89
+
90
+ all
91
+ end
92
+
93
+ def self.with_runtime_info(tests)
94
+ lines = File.read(runtime_log).split("\n") rescue []
95
+
96
+ # use recorded test runtime if we got enough data
97
+ if lines.size * 1.5 > tests.size
98
+ puts "Using recorded test runtime"
99
+ times = Hash.new(1)
100
+ lines.each do |line|
101
+ test, time = line.split(":")
102
+ next unless test and time
103
+ times[File.expand_path(test)] = time.to_f
104
+ end
105
+ tests.sort.map{|test| [test, times[File.expand_path(test)]] }
106
+ else # use file sizes
107
+ tests.sort.map{|test| [test, File.stat(test).size] }
108
+ end
109
+ end
110
+
111
+ def self.find_tests(tests, options = {})
112
+ (tests || []).map do |file_or_folder|
113
+ if File.directory?(file_or_folder)
114
+ files = files_in_folder(file_or_folder, options)
115
+ files.grep(/#{Regexp.escape test_suffix}$/).grep(options[:pattern]||//)
116
+ else
117
+ file_or_folder
118
+ end
119
+ end.flatten.uniq
120
+ end
121
+
122
+ def self.files_in_folder(folder, options={})
123
+ pattern = if options[:symlinks] == false # not nil or true
124
+ "**/*"
125
+ else
126
+ # follow one symlink and direct children
127
+ # http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
128
+ "**{,/*/**}/*"
129
+ end
130
+ Dir[File.join(folder, pattern)].uniq
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,92 @@
1
+ require 'parallel_tests/test/runner'
2
+
3
+ module ParallelTests
4
+ module Test
5
+ class RuntimeLogger
6
+ @@has_started = false
7
+
8
+ def self.log(test, start_time, end_time)
9
+ return if test.is_a? ::Test::Unit::TestSuite # don't log for suites-of-suites
10
+
11
+ if !@@has_started # make empty log file
12
+ File.open(logfile, 'w'){}
13
+ @@has_started = true
14
+ end
15
+
16
+ locked_appending_to(logfile) do |file|
17
+ file.puts(message(test, start_time, end_time))
18
+ end
19
+ end
20
+
21
+ def self.message(test, start_time, end_time)
22
+ delta = "%.2f" % (end_time.to_f-start_time.to_f)
23
+ filename = class_directory(test.class) + class_to_filename(test.class) + ".rb"
24
+ "#{filename}:#{delta}"
25
+ end
26
+
27
+ # Note: this is a best guess at conventional test directory structure, and may need
28
+ # tweaking / post-processing to match correctly for any given project
29
+ def self.class_directory(suspect)
30
+ result = "test/"
31
+
32
+ if defined?(Rails)
33
+ result += case suspect.superclass.name
34
+ when "ActionDispatch::IntegrationTest"
35
+ "integration/"
36
+ when "ActionDispatch::PerformanceTest"
37
+ "performance/"
38
+ when "ActionController::TestCase"
39
+ "functional/"
40
+ when "ActionView::TestCase"
41
+ "unit/helpers/"
42
+ else
43
+ "unit/"
44
+ end
45
+ end
46
+ result
47
+ end
48
+
49
+ # based on https://github.com/grosser/single_test/blob/master/lib/single_test.rb#L117
50
+ def self.class_to_filename(suspect)
51
+ word = suspect.to_s.dup
52
+ return word unless word.match /^[A-Z]/ and not word.match %r{/[a-z]}
53
+
54
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
55
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
56
+ word.gsub!(/\:\:/, '/')
57
+ word.tr!("-", "_")
58
+ word.downcase!
59
+ word
60
+ end
61
+
62
+ def self.locked_appending_to(file)
63
+ File.open(file, 'a') do |f|
64
+ begin
65
+ f.flock File::LOCK_EX
66
+ yield f
67
+ ensure
68
+ f.flock File::LOCK_UN
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.logfile
74
+ ParallelTests::Test::Runner.runtime_log
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ require 'test/unit/testsuite'
81
+ class ::Test::Unit::TestSuite
82
+ alias :run_without_timing :run unless defined? @@timing_installed
83
+
84
+ def run(result, &progress_block)
85
+ start_time=Time.now
86
+ run_without_timing(result, &progress_block)
87
+ end_time=Time.now
88
+ ParallelTests::Test::RuntimeLogger.log(self.tests.first, start_time, end_time)
89
+ end
90
+
91
+ @@timing_installed = true
92
+ end
@@ -0,0 +1,3 @@
1
+ module ParallelTests
2
+ VERSION = Version = '0.9.0'
3
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
+ name = "friendlyfashion-parallel_tests"
3
+ require "parallel_tests/version"
4
+
5
+ Gem::Specification.new name, ParallelTests::VERSION do |s|
6
+ s.summary = "Run Test::Unit / RSpec / Cucumber in parallel"
7
+ s.authors = ["Laurynas Butkus", "Tomas Varaneckas", "Justas Janauskas"]
8
+ s.email = ["laurynas.butkus@gmail.com", "tomas.varaneckas@gmail.com", "jjanauskas@gmail.com"]
9
+ s.homepage = "http://github.com/friendlyfashion/parallel_tests"
10
+ s.files = `git ls-files`.split("\n")
11
+ s.license = "MIT"
12
+ s.executables = ["parallel_cucumber", "parallel_rspec", "parallel_test"]
13
+ s.add_runtime_dependency "parallel"
14
+ end
@@ -0,0 +1,244 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'CLI' do
4
+ before do
5
+ `rm -rf #{folder}`
6
+ end
7
+
8
+ after do
9
+ `rm -rf #{folder}`
10
+ end
11
+
12
+ def folder
13
+ "/tmp/parallel_tests_tests"
14
+ end
15
+
16
+ def write(file, content)
17
+ path = "#{folder}/#{file}"
18
+ ensure_folder File.dirname(path)
19
+ File.open(path, 'w'){|f| f.write content }
20
+ path
21
+ end
22
+
23
+ def read(file)
24
+ File.read "#{folder}/#{file}"
25
+ end
26
+
27
+ def bin_folder
28
+ "#{File.expand_path(File.dirname(__FILE__))}/../bin"
29
+ end
30
+
31
+ def executable(options={})
32
+ "#{bin_folder}/parallel_#{options[:type] || 'test'}"
33
+ end
34
+
35
+ def ensure_folder(folder)
36
+ `mkdir -p #{folder}` unless File.exist?(folder)
37
+ end
38
+
39
+ def run_tests(test_folder, options={})
40
+ ensure_folder folder
41
+ processes = "-n #{options[:processes]||2}" unless options[:processes] == false
42
+ command = "cd #{folder} && #{options[:export]} #{executable(options)} #{test_folder} #{processes} #{options[:add]} 2>&1"
43
+ result = `#{command}`
44
+ raise "FAILED #{command}\n#{result}" if $?.success? == !!options[:fail]
45
+ result
46
+ end
47
+
48
+ it "runs tests in parallel" do
49
+ write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
50
+ write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
51
+ result = run_tests "spec", :type => 'rspec'
52
+
53
+ # test ran and gave their puts
54
+ result.should include('TEST1')
55
+ result.should include('TEST2')
56
+
57
+ # all results present
58
+ result.scan('1 example, 0 failure').size.should == 2 # 2 results
59
+ result.scan('2 examples, 0 failures').size.should == 1 # 1 summary
60
+ result.scan(/Finished in \d+\.\d+ seconds/).size.should == 2
61
+ result.scan(/Took \d+\.\d+ seconds/).size.should == 1 # parallel summary
62
+ end
63
+
64
+ it "does not run any tests if there are none" do
65
+ write 'spec/xxx_spec.rb', '1'
66
+ result = run_tests "spec", :type => 'rspec'
67
+ result.should include('No examples found')
68
+ result.should include('Took')
69
+ end
70
+
71
+ it "fails when tests fail" do
72
+ write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
73
+ write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){1.should == 2}}'
74
+ result = run_tests "spec", :fail => true, :type => 'rspec'
75
+
76
+ result.scan('1 example, 1 failure').size.should == 1
77
+ result.scan('1 example, 0 failure').size.should == 1
78
+ result.scan('2 examples, 1 failure').size.should == 1
79
+ end
80
+
81
+ context "with given commands" do
82
+ it "can exec given commands with ENV['TEST_ENV_NUM']" do
83
+ result = `#{executable} -e 'ruby -e "print ENV[:TEST_ENV_NUMBER.to_s].to_i"' -n 4`
84
+ result.gsub('"','').split('').sort.should == %w[0 2 3 4]
85
+ end
86
+
87
+ it "can exec given command non-parallel" do
88
+ result = `#{executable} -e 'ruby -e "sleep(rand(10)/100.0); puts ENV[:TEST_ENV_NUMBER.to_s].inspect"' -n 4 --non-parallel`
89
+ result.split("\n").should == %w["" "2" "3" "4"]
90
+ end
91
+
92
+ it "exists with success if all sub-processes returned success" do
93
+ system("#{executable} -e 'cat /dev/null' -n 4").should == true
94
+ end
95
+
96
+ it "exists with failure if any sub-processes returned failure" do
97
+ system("#{executable} -e 'test -e xxxx' -n 4").should == false
98
+ end
99
+ end
100
+
101
+ it "runs through parallel_rspec" do
102
+ version = `#{executable} -v`
103
+ `#{bin_folder}/parallel_rspec -v`.should == version
104
+ end
105
+
106
+ it "runs through parallel_cucumber" do
107
+ version = `#{executable} -v`
108
+ `#{bin_folder}/parallel_cucumber -v`.should == version
109
+ end
110
+
111
+ it "runs with --group-by found" do
112
+ # it only tests that it does not blow up, as it did before fixing...
113
+ write "spec/x1_spec.rb", "puts '111'"
114
+ run_tests "spec", :type => 'rspec', :add => '--group-by found'
115
+ end
116
+
117
+ it "runs faster with more processes" do
118
+ 2.times{|i|
119
+ write "spec/xxx#{i}_spec.rb", 'describe("it"){it("should"){sleep 5}}; $stderr.puts ENV["TEST_ENV_NUMBER"]'
120
+ }
121
+ t = Time.now
122
+ run_tests("spec", :processes => 2, :type => 'rspec')
123
+ expected = 10
124
+ (Time.now - t).should <= expected
125
+ end
126
+
127
+ it "can run with given files" do
128
+ write "spec/x1_spec.rb", "puts '111'"
129
+ write "spec/x2_spec.rb", "puts '222'"
130
+ write "spec/x3_spec.rb", "puts '333'"
131
+ result = run_tests "spec/x1_spec.rb spec/x3_spec.rb", :type => 'rspec'
132
+ result.should include('111')
133
+ result.should include('333')
134
+ result.should_not include('222')
135
+ end
136
+
137
+ it "runs successfully without any files" do
138
+ results = run_tests "", :type => 'rspec'
139
+ results.should include("2 processes for 0 specs")
140
+ results.should include("Took")
141
+ end
142
+
143
+ it "can run with test-options" do
144
+ write "spec/x1_spec.rb", "111"
145
+ write "spec/x2_spec.rb", "111"
146
+ result = run_tests "spec",
147
+ :add => "--test-options ' --version'",
148
+ :processes => 2,
149
+ :type => 'rspec'
150
+ result.should =~ /\d+\.\d+\.\d+.*\d+\.\d+\.\d+/m # prints version twice
151
+ end
152
+
153
+ it "runs with PARALLEL_TEST_PROCESSORS processes" do
154
+ processes = 5
155
+ processes.times{|i|
156
+ write "spec/x#{i}_spec.rb", "puts %{ENV-\#{ENV['TEST_ENV_NUMBER']}-}"
157
+ }
158
+ result = run_tests "spec",
159
+ :export => "PARALLEL_TEST_PROCESSORS=#{processes}",
160
+ :processes => processes,
161
+ :type => 'rspec'
162
+ result.scan(/ENV-.?-/).should =~ ["ENV--", "ENV-2-", "ENV-3-", "ENV-4-", "ENV-5-"]
163
+ end
164
+
165
+ it "filters test by given pattern and relative paths" do
166
+ write "spec/x_spec.rb", "puts 'XXX'"
167
+ write "spec/y_spec.rb", "puts 'YYY'"
168
+ write "spec/z_spec.rb", "puts 'ZZZ'"
169
+ result = run_tests "spec", :add => "-p '^spec/(x|z)'", :type => "rspec"
170
+ result.should include('XXX')
171
+ result.should_not include('YYY')
172
+ result.should include('ZZZ')
173
+ end
174
+
175
+ context "Test::Unit" do
176
+ it "runs" do
177
+ write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
178
+ result = run_tests("test")
179
+ result.should include('1 test')
180
+ end
181
+
182
+ it "passes test options" do
183
+ write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
184
+ result = run_tests("test", :add => '--test-options "-v"')
185
+ result.should include('test_xxx') # verbose output of every test
186
+ end
187
+
188
+ it "runs successfully without any files" do
189
+ results = run_tests("")
190
+ results.should include("2 processes for 0 tests")
191
+ results.should include("Took")
192
+ end
193
+ end
194
+
195
+ context "Cucumber" do
196
+ before do
197
+ write "features/steps/a.rb", "
198
+ Given('I print TEST_ENV_NUMBER'){ puts \"YOUR TEST ENV IS \#{ENV['TEST_ENV_NUMBER']}!\" }
199
+ And('I sleep a bit'){ sleep 0.2 }
200
+ "
201
+ end
202
+
203
+ it "passes TEST_ENV_NUMBER when running with pattern (issue #86)" do
204
+ write "features/good1.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
205
+ write "features/good2.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
206
+ write "features/b.feature", "Feature: xxx\n Scenario: xxx\n Given I FAIL"
207
+ write "features/steps/a.rb", "Given('I print TEST_ENV_NUMBER'){ puts \"YOUR TEST ENV IS \#{ENV['TEST_ENV_NUMBER']}!\" }"
208
+
209
+ result = run_tests "features", :type => "cucumber", :add => '--pattern good'
210
+
211
+ result.should include('YOUR TEST ENV IS 2!')
212
+ result.should include('YOUR TEST ENV IS !')
213
+ result.should_not include('I FAIL')
214
+ end
215
+
216
+ it "writes a runtime log" do
217
+ log = "tmp/parallel_runtime_cucumber.log"
218
+ write(log, "x")
219
+ 2.times{|i|
220
+ # needs sleep so that runtime loggers dont overwrite each other initially
221
+ write "features/good#{i}.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER\n And I sleep a bit"
222
+ }
223
+ run_tests "features", :type => "cucumber"
224
+ read(log).gsub(/\.\d+/,'').split("\n").should =~ [
225
+ "features/good0.feature:0",
226
+ "features/good1.feature:0"
227
+ ]
228
+ end
229
+
230
+ it "runs each feature once when there are more processes then features (issue #89)" do
231
+ 2.times{|i|
232
+ write "features/good#{i}.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
233
+ }
234
+ result = run_tests "features", :type => "cucumber", :add => '-n 3'
235
+ result.scan(/YOUR TEST ENV IS \d?!/).sort.should == ["YOUR TEST ENV IS !", "YOUR TEST ENV IS 2!"]
236
+ end
237
+
238
+ it "runs successfully without any files" do
239
+ results = run_tests("", :type => "cucumber")
240
+ results.should include("2 processes for 0 features")
241
+ results.should include("Took")
242
+ end
243
+ end
244
+ end