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,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