friendlyfashion-parallel_tests 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParallelTests::RSpec::RuntimeLogger do
4
+ before do
5
+ # pretend we run in parallel or the logger will log nothing
6
+ ENV['TEST_ENV_NUMBER'] = ''
7
+ @clean_output = %r{^spec/foo.rb:[-\.e\d]+$}m
8
+ end
9
+
10
+ after do
11
+ ENV.delete 'TEST_ENV_NUMBER'
12
+ end
13
+
14
+ def log_for_a_file(options={})
15
+ Tempfile.open('xxx') do |temp|
16
+ temp.close
17
+ f = File.open(temp.path,'w')
18
+ logger = if block_given?
19
+ yield(f)
20
+ else
21
+ ParallelTests::RSpec::RuntimeLogger.new(f)
22
+ end
23
+
24
+ example = (mock(:location => "#{Dir.pwd}/spec/foo.rb:123"))
25
+ logger.example_started example
26
+ logger.example_passed example
27
+ if options[:pending]
28
+ logger.example_pending example
29
+ logger.dump_pending
30
+ end
31
+ if options[:failed]
32
+ logger.example_failed example
33
+ logger.dump_failures
34
+ end
35
+ logger.start_dump
36
+
37
+ #f.close
38
+ return File.read(f.path)
39
+ end
40
+ end
41
+
42
+ it "logs runtime with relative paths" do
43
+ log_for_a_file.should =~ @clean_output
44
+ end
45
+
46
+ it "does not log pending" do
47
+ log_for_a_file(:pending => true).should =~ @clean_output
48
+ end
49
+
50
+ it "does not log failures" do
51
+ log_for_a_file(:failed => true).should =~ @clean_output
52
+ end
53
+
54
+ it "does not log if we do not run in parallel" do
55
+ ENV.delete 'TEST_ENV_NUMBER'
56
+ log_for_a_file.should == ""
57
+ end
58
+
59
+ it "appends to a given file" do
60
+ result = log_for_a_file do |f|
61
+ f.write 'FooBar'
62
+ ParallelTests::RSpec::RuntimeLogger.new(f)
63
+ end
64
+ result.should include('FooBar')
65
+ result.should include('foo.rb')
66
+ end
67
+
68
+ it "overwrites a given path" do
69
+ result = log_for_a_file do |f|
70
+ f.write 'FooBar'
71
+ ParallelTests::RSpec::RuntimeLogger.new(f.path)
72
+ end
73
+ result.should_not include('FooBar')
74
+ result.should include('foo.rb')
75
+ end
76
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParallelTests::RSpec::SummaryLogger do
4
+ let(:output){ OutputLogger.new([]) }
5
+ let(:logger){ ParallelTests::RSpec::SummaryLogger.new(output) }
6
+
7
+ def decolorize(string)
8
+ string.gsub(/\e\[\d+m/,'')
9
+ end
10
+
11
+ # TODO somehow generate a real example with an exception to test this
12
+ xit "prints failing examples" do
13
+ logger.example_failed XXX
14
+ logger.example_failed XXX
15
+ logger.dump_failures
16
+ output.output.should == [
17
+ "bundle exec rspec ./spec/path/to/example.rb:123 # should do stuff",
18
+ "bundle exec rspec ./spec/path/to/example.rb:125 # should not do stuff"
19
+ ]
20
+ end
21
+
22
+ it "does not print anything for passing examples" do
23
+ logger.example_passed mock(:location => "/my/spec/foo.rb:123")
24
+ logger.dump_failures
25
+ output.output.should == []
26
+ logger.dump_summary(1,2,3,4)
27
+ output.output.map{|o| decolorize(o) }.should == ["\nFinished in 1 second\n", "2 examples, 3 failures, 4 pending"]
28
+ end
29
+
30
+ it "does not print anything for pending examples" do
31
+ logger.example_pending mock(:location => "/my/spec/foo.rb:123")
32
+ logger.dump_failures
33
+ output.output.should == []
34
+ logger.dump_summary(1,2,3,4)
35
+ output.output.map{|o| decolorize(o) }.should == ["\nFinished in 1 second\n", "2 examples, 3 failures, 4 pending"]
36
+ end
37
+ end
@@ -0,0 +1,151 @@
1
+ require 'spec_helper'
2
+ require 'parallel_tests/tasks'
3
+
4
+ describe ParallelTests::Tasks do
5
+ describe ".parse_args" do
6
+ it "should return the count" do
7
+ args = {:count => 2}
8
+ ParallelTests::Tasks.parse_args(args).should == [2, "", ""]
9
+ end
10
+
11
+ it "should default to the prefix" do
12
+ args = {:count => "models"}
13
+ ParallelTests::Tasks.parse_args(args).should == [nil, "models", ""]
14
+ end
15
+
16
+ it "should return the count and pattern" do
17
+ args = {:count => 2, :pattern => "models"}
18
+ ParallelTests::Tasks.parse_args(args).should == [2, "models", ""]
19
+ end
20
+
21
+ it "should return the count, pattern, and options" do
22
+ args = {:count => 2, :pattern => "plain", :options => "-p default"}
23
+ ParallelTests::Tasks.parse_args(args).should == [2, "plain", "-p default"]
24
+ end
25
+ end
26
+
27
+ describe ".rails_env" do
28
+ around do |example|
29
+ begin
30
+ old = ENV["RAILS_ENV"]
31
+ ENV.delete "RAILS_ENV"
32
+ example.call
33
+ ensure
34
+ ENV["RAILS_ENV"] = old
35
+ end
36
+ end
37
+
38
+ it "should be test when nothing was set" do
39
+ ParallelTests::Tasks.rails_env.should == "test"
40
+ end
41
+
42
+ it "should be whatever was set" do
43
+ ENV["RAILS_ENV"] = "foo"
44
+ ParallelTests::Tasks.rails_env.should == "foo"
45
+ end
46
+ end
47
+
48
+ describe ".run_in_parallel" do
49
+ let(:full_path){ File.expand_path("../../../bin/parallel_test", __FILE__) }
50
+
51
+ it "should have the executable" do
52
+ File.file?(full_path).should == true
53
+ File.executable?(full_path).should == true
54
+ end
55
+
56
+ it "runs command in parallel" do
57
+ ParallelTests::Tasks.should_receive(:system).with("#{full_path} --exec 'echo'").and_return true
58
+ ParallelTests::Tasks.run_in_parallel("echo")
59
+ end
60
+
61
+ it "runs command with :count option" do
62
+ ParallelTests::Tasks.should_receive(:system).with("#{full_path} --exec 'echo' -n 123").and_return true
63
+ ParallelTests::Tasks.run_in_parallel("echo", :count => 123)
64
+ end
65
+
66
+ it "runs command with :non_parallel option" do
67
+ ParallelTests::Tasks.should_receive(:system).with("#{full_path} --exec 'echo' --non-parallel").and_return true
68
+ ParallelTests::Tasks.run_in_parallel("echo", :non_parallel => true)
69
+ end
70
+
71
+ it "runs aborts if the command fails" do
72
+ ParallelTests::Tasks.should_receive(:system).and_return false
73
+ ParallelTests::Tasks.should_receive(:abort).and_return false
74
+ ParallelTests::Tasks.run_in_parallel("echo")
75
+ end
76
+ end
77
+
78
+ describe ".suppress_output" do
79
+ def call(command, grep)
80
+ result = `#{ParallelTests::Tasks.suppress_output(command, grep)}`
81
+ [result, $?.success?]
82
+ end
83
+
84
+ context "with pipefail supported" do
85
+ before :all do
86
+ if not system("set -o pipefail && test 1")
87
+ pending "pipefail is not supported on your system"
88
+ end
89
+ end
90
+
91
+ it "should hide offending lines" do
92
+ call("echo 123", "123").should == ["", true]
93
+ end
94
+
95
+ it "should not hide other lines" do
96
+ call("echo 124", "123").should == ["124\n", true]
97
+ end
98
+
99
+ it "should fail if command fails and the pattern matches" do
100
+ call("echo 123 && test", "123").should == ["", false]
101
+ end
102
+
103
+ it "should fail if command fails and the pattern fails" do
104
+ call("echo 124 && test", "123").should == ["124\n", false]
105
+ end
106
+ end
107
+
108
+ context "without pipefail supported" do
109
+ before do
110
+ ParallelTests::Tasks.should_receive(:system).with("set -o pipefail && test 1").and_return false
111
+ end
112
+
113
+ it "should not filter and succeed" do
114
+ call("echo 123", "123").should == ["123\n", true]
115
+ end
116
+
117
+ it "should not filter and fail" do
118
+ call("echo 123 && test", "123").should == ["123\n", false]
119
+ end
120
+ end
121
+ end
122
+
123
+ describe ".check_for_pending_migrations" do
124
+ after do
125
+ Rake.application.instance_variable_get('@tasks').delete("db:abort_if_pending_migrations")
126
+ end
127
+
128
+ it "should do nothing if pending migrations is no defined" do
129
+ ParallelTests::Tasks.check_for_pending_migrations
130
+ end
131
+
132
+ it "should run pending migrations is task is defined" do
133
+ foo = 1
134
+ Rake::Task.define_task("db:abort_if_pending_migrations") do
135
+ foo = 2
136
+ end
137
+ ParallelTests::Tasks.check_for_pending_migrations
138
+ foo.should == 2
139
+ end
140
+
141
+ it "should not execute the task twice" do
142
+ foo = 1
143
+ Rake::Task.define_task("db:abort_if_pending_migrations") do
144
+ foo += 1
145
+ end
146
+ ParallelTests::Tasks.check_for_pending_migrations
147
+ ParallelTests::Tasks.check_for_pending_migrations
148
+ foo.should == 2
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,273 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParallelTests::Test::Runner do
4
+ test_tests_in_groups(ParallelTests::Test::Runner, 'test', '_test.rb')
5
+
6
+ describe :run_tests do
7
+ def call(*args)
8
+ ParallelTests::Test::Runner.run_tests(*args)
9
+ end
10
+
11
+ it "uses TEST_ENV_NUMBER=blank when called for process 0" do
12
+ ParallelTests::Test::Runner.should_receive(:open).with{|x,y|x=~/TEST_ENV_NUMBER= /}.and_return mocked_process
13
+ call(['xxx'],0,{})
14
+ end
15
+
16
+ it "uses TEST_ENV_NUMBER=2 when called for process 1" do
17
+ ParallelTests::Test::Runner.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mocked_process
18
+ call(['xxx'],1,{})
19
+ end
20
+
21
+ it "uses options" do
22
+ ParallelTests::Test::Runner.should_receive(:open).with{|x,y| x=~ %r{ruby -Itest .* -- -v}}.and_return mocked_process
23
+ call(['xxx'],1,:test_options => '-v')
24
+ end
25
+
26
+ it "returns the output" do
27
+ io = open('spec/spec_helper.rb')
28
+ $stdout.stub!(:print)
29
+ ParallelTests::Test::Runner.should_receive(:open).and_return io
30
+ call(['xxx'],1,{})[:stdout].should =~ /\$LOAD_PATH << File/
31
+ end
32
+ end
33
+
34
+ describe :test_in_groups do
35
+ def call(*args)
36
+ ParallelTests::Test::Runner.tests_in_groups(*args)
37
+ end
38
+
39
+ it "does not sort when passed false do_sort option" do
40
+ ParallelTests::Test::Runner.should_not_receive(:smallest_first)
41
+ call([], 1, :group_by => :found)
42
+ end
43
+
44
+ it "does sort when not passed do_sort option" do
45
+ ParallelTests::Test::Runner.stub!(:tests_with_runtime).and_return([])
46
+ ParallelTests::Grouper.should_receive(:largest_first).and_return([])
47
+ call([], 1)
48
+ end
49
+
50
+ it "groups by single_process pattern and then via size" do
51
+ ParallelTests::Test::Runner.should_receive(:with_runtime_info).
52
+ and_return([
53
+ ['aaa', 5],
54
+ ['aaa2', 5],
55
+ ['bbb', 2],
56
+ ['ccc', 1],
57
+ ['ddd', 1]
58
+ ])
59
+ result = call([], 3, :single_process => [/^a.a/])
60
+ result.should == [["aaa", "aaa2"], ["bbb"], ["ccc", "ddd"]]
61
+ end
62
+
63
+ it "groups by size and adds isolated separately" do
64
+ ParallelTests::Test::Runner.should_receive(:with_runtime_info).
65
+ and_return([
66
+ ['aaa', 0],
67
+ ['bbb', 3],
68
+ ['ccc', 1],
69
+ ['ddd', 2],
70
+ ['eee', 1]
71
+ ])
72
+
73
+ result = call([], 3, :isolate => true, :single_process => [/^aaa/])
74
+ result.should == [["aaa"], ["bbb", "eee"], ["ccc", "ddd"]]
75
+ end
76
+ end
77
+
78
+ describe :find_results do
79
+ def call(*args)
80
+ ParallelTests::Test::Runner.find_results(*args)
81
+ end
82
+
83
+ it "finds multiple results in test output" do
84
+ output = <<EOF
85
+ Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
86
+ Started
87
+ ..............
88
+ Finished in 0.145069 seconds.
89
+
90
+ 10 tests, 20 assertions, 0 failures, 0 errors
91
+ Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
92
+ Started
93
+ ..............
94
+ Finished in 0.145069 seconds.
95
+
96
+ 14 tests, 20 assertions, 0 failures, 0 errors
97
+
98
+ EOF
99
+
100
+ call(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors','14 tests, 20 assertions, 0 failures, 0 errors']
101
+ end
102
+
103
+ it "is robust against scrambled output" do
104
+ output = <<EOF
105
+ Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
106
+ Started
107
+ ..............
108
+ Finished in 0.145069 seconds.
109
+
110
+ 10 tests, 20 assertions, 0 failures, 0 errors
111
+ Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
112
+ Started
113
+ ..............
114
+ Finished in 0.145069 seconds.
115
+
116
+ 14 te.dsts, 20 assertions, 0 failures, 0 errors
117
+ EOF
118
+
119
+ call(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors','14 tedsts, 20 assertions, 0 failures, 0 errors']
120
+ end
121
+
122
+ it "ignores color-codes" do
123
+ output = <<EOF
124
+ 10 tests, 20 assertions, 0 \e[31mfailures, 0 errors
125
+ EOF
126
+ call(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors']
127
+ end
128
+ end
129
+
130
+ describe :find_tests do
131
+ def call(*args)
132
+ ParallelTests::Test::Runner.send(:find_tests, *args)
133
+ end
134
+
135
+ def with_files(files)
136
+ begin
137
+ root = "/tmp/test-find_tests-#{rand(999)}"
138
+ `mkdir #{root}`
139
+ files.each do |file|
140
+ parent = "#{root}/#{File.dirname(file)}"
141
+ `mkdir -p #{parent}` unless File.exist?(parent)
142
+ `touch #{root}/#{file}`
143
+ end
144
+ yield root
145
+ ensure
146
+ `rm -rf #{root}`
147
+ end
148
+ end
149
+
150
+ def inside_dir(dir)
151
+ old = Dir.pwd
152
+ Dir.chdir dir
153
+ yield
154
+ ensure
155
+ Dir.chdir old
156
+ end
157
+
158
+ it "finds test in folders with appended /" do
159
+ with_files(['b/a_test.rb']) do |root|
160
+ call(["#{root}/"]).sort.should == [
161
+ "#{root}/b/a_test.rb",
162
+ ]
163
+ end
164
+ end
165
+
166
+ it "finds test files nested in symlinked folders" do
167
+ with_files(['a/a_test.rb','b/b_test.rb']) do |root|
168
+ `ln -s #{root}/a #{root}/b/link`
169
+ call(["#{root}/b"]).sort.should == [
170
+ "#{root}/b/b_test.rb",
171
+ "#{root}/b/link/a_test.rb",
172
+ ]
173
+ end
174
+ end
175
+
176
+ it "finds test files but ignores those in symlinked folders" do
177
+ with_files(['a/a_test.rb','b/b_test.rb']) do |root|
178
+ `ln -s #{root}/a #{root}/b/link`
179
+ call(["#{root}/b"], :symlinks => false).sort.should == [
180
+ "#{root}/b/b_test.rb",
181
+ ]
182
+ end
183
+ end
184
+
185
+ it "finds test files nested in different folders" do
186
+ with_files(['a/a_test.rb','b/b_test.rb', 'c/c_test.rb']) do |root|
187
+ call(["#{root}/a", "#{root}/b"]).sort.should == [
188
+ "#{root}/a/a_test.rb",
189
+ "#{root}/b/b_test.rb",
190
+ ]
191
+ end
192
+ end
193
+
194
+ it "only finds tests in folders" do
195
+ with_files(['a/a_test.rb', 'a/test.rb', 'a/test_helper.rb']) do |root|
196
+ call(["#{root}/a"]).sort.should == [
197
+ "#{root}/a/a_test.rb"
198
+ ]
199
+ end
200
+ end
201
+
202
+ it "finds tests in nested folders" do
203
+ with_files(['a/b/c/d/a_test.rb']) do |root|
204
+ call(["#{root}/a"]).sort.should == [
205
+ "#{root}/a/b/c/d/a_test.rb"
206
+ ]
207
+ end
208
+ end
209
+
210
+ it "does not expand paths" do
211
+ with_files(['a/x_test.rb']) do |root|
212
+ inside_dir root do
213
+ call(['a']).sort.should == [
214
+ "a/x_test.rb"
215
+ ]
216
+ end
217
+ end
218
+ end
219
+
220
+ it "finds test files in folders by pattern" do
221
+ with_files(['a/x_test.rb','a/y_test.rb','a/z_test.rb']) do |root|
222
+ inside_dir root do
223
+ call(["a"], :pattern => /^a\/(y|z)_test/).sort.should == [
224
+ "a/y_test.rb",
225
+ "a/z_test.rb",
226
+ ]
227
+ end
228
+ end
229
+ end
230
+
231
+ it "finds nothing if I pass nothing" do
232
+ call(nil).should == []
233
+ end
234
+
235
+ it "finds nothing if I pass nothing (empty array)" do
236
+ call([]).should == []
237
+ end
238
+
239
+ it "keeps invalid files" do
240
+ call(['baz']).should == ['baz']
241
+ end
242
+
243
+ it "discards duplicates" do
244
+ call(['baz','baz']).should == ['baz']
245
+ end
246
+ end
247
+
248
+ describe :summarize_results do
249
+ def call(*args)
250
+ ParallelTests::Test::Runner.summarize_results(*args)
251
+ end
252
+
253
+ it "adds results" do
254
+ call(['1 foo 3 bar','2 foo 5 bar']).should == '8 bars, 3 foos'
255
+ end
256
+
257
+ it "adds results with braces" do
258
+ call(['1 foo(s) 3 bar(s)','2 foo 5 bar']).should == '8 bars, 3 foos'
259
+ end
260
+
261
+ it "adds same results with plurals" do
262
+ call(['1 foo 3 bar','2 foos 5 bar']).should == '8 bars, 3 foos'
263
+ end
264
+
265
+ it "adds non-similar results" do
266
+ call(['1 xxx 2 yyy','1 xxx 2 zzz']).should == '2 xxxs, 2 yyys, 2 zzzs'
267
+ end
268
+
269
+ it "does not pluralize 1" do
270
+ call(['1 xxx 2 yyy']).should == '1 xxx, 2 yyys'
271
+ end
272
+ end
273
+ end