test-queue-patched 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +18 -0
- data/Gemfile +5 -0
- data/Gemfile-cucumber1-3 +4 -0
- data/Gemfile-cucumber1-3.lock +33 -0
- data/Gemfile-cucumber2-4 +4 -0
- data/Gemfile-cucumber2-4.lock +37 -0
- data/Gemfile-minitest4 +3 -0
- data/Gemfile-minitest4.lock +19 -0
- data/Gemfile-minitest5 +3 -0
- data/Gemfile-minitest5.lock +19 -0
- data/Gemfile-rspec2-1 +3 -0
- data/Gemfile-rspec2-1.lock +27 -0
- data/Gemfile-rspec3-0 +3 -0
- data/Gemfile-rspec3-0.lock +31 -0
- data/Gemfile-rspec3-1 +3 -0
- data/Gemfile-rspec3-1.lock +31 -0
- data/Gemfile-rspec3-2 +3 -0
- data/Gemfile-rspec3-2.lock +32 -0
- data/Gemfile-testunit +3 -0
- data/Gemfile-testunit.lock +21 -0
- data/Gemfile.lock +41 -0
- data/README.md +126 -0
- data/bin/cucumber-queue +4 -0
- data/bin/minitest-queue +4 -0
- data/bin/rspec-queue +4 -0
- data/bin/testunit-queue +4 -0
- data/lib/test-queue.rb +1 -0
- data/lib/test_queue/iterator.rb +107 -0
- data/lib/test_queue/runner/cucumber.rb +115 -0
- data/lib/test_queue/runner/minitest.rb +21 -0
- data/lib/test_queue/runner/minitest4.rb +88 -0
- data/lib/test_queue/runner/minitest5.rb +87 -0
- data/lib/test_queue/runner/puppet_lint.rb +31 -0
- data/lib/test_queue/runner/rspec.rb +79 -0
- data/lib/test_queue/runner/rspec2.rb +44 -0
- data/lib/test_queue/runner/rspec3.rb +54 -0
- data/lib/test_queue/runner/sample.rb +74 -0
- data/lib/test_queue/runner/testunit.rb +74 -0
- data/lib/test_queue/runner.rb +632 -0
- data/lib/test_queue/stats.rb +95 -0
- data/lib/test_queue/test_framework.rb +29 -0
- data/lib/test_queue.rb +8 -0
- data/script/bootstrap +12 -0
- data/script/cibuild +19 -0
- data/script/spec +7 -0
- data/spec/stats_spec.rb +76 -0
- data/test/cucumber.bats +57 -0
- data/test/minitest4.bats +34 -0
- data/test/minitest5.bats +194 -0
- data/test/rspec.bats +46 -0
- data/test/samples/features/bad.feature +5 -0
- data/test/samples/features/sample.feature +25 -0
- data/test/samples/features/sample2.feature +29 -0
- data/test/samples/features/step_definitions/common.rb +19 -0
- data/test/samples/sample_minispec.rb +37 -0
- data/test/samples/sample_minitest4.rb +25 -0
- data/test/samples/sample_minitest5.rb +33 -0
- data/test/samples/sample_rspec_helper.rb +1 -0
- data/test/samples/sample_shared_examples_for_spec.rb +5 -0
- data/test/samples/sample_spec.rb +25 -0
- data/test/samples/sample_split_spec.rb +17 -0
- data/test/samples/sample_testunit.rb +25 -0
- data/test/samples/sample_use_shared_example1_spec.rb +8 -0
- data/test/samples/sample_use_shared_example2_spec.rb +8 -0
- data/test/sleepy_runner.rb +14 -0
- data/test/testlib.bash +89 -0
- data/test/testunit.bats +20 -0
- data/test-queue-patched.gemspec +21 -0
- metadata +117 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'cucumber'
|
2
|
+
require 'cucumber/rspec/disable_option_parser'
|
3
|
+
require 'cucumber/cli/main'
|
4
|
+
|
5
|
+
module Cucumber
|
6
|
+
module Ast
|
7
|
+
class Features
|
8
|
+
attr_accessor :features
|
9
|
+
end
|
10
|
+
|
11
|
+
class Feature
|
12
|
+
def to_s
|
13
|
+
title
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Runtime
|
19
|
+
if defined?(::Cucumber::Runtime::FeaturesLoader)
|
20
|
+
# Without this module, Runtime#features would load all features specified
|
21
|
+
# on the command line. We want to avoid that and load only the features
|
22
|
+
# each worker needs ourselves, so we override the default behavior to let
|
23
|
+
# us put our iterator in place without loading any features directly.
|
24
|
+
module InjectableFeatures
|
25
|
+
def features
|
26
|
+
return @features if defined?(@features)
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def features=(iterator)
|
31
|
+
@features = ::Cucumber::Ast::Features.new
|
32
|
+
@features.features = iterator
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
prepend InjectableFeatures
|
37
|
+
else
|
38
|
+
attr_writer :features
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module TestQueue
|
44
|
+
class Runner
|
45
|
+
class Cucumber < Runner
|
46
|
+
def initialize
|
47
|
+
super(TestFramework::Cucumber.new)
|
48
|
+
end
|
49
|
+
|
50
|
+
def run_worker(iterator)
|
51
|
+
runtime = @test_framework.runtime
|
52
|
+
runtime.features = iterator
|
53
|
+
|
54
|
+
@test_framework.cli.execute!(runtime)
|
55
|
+
|
56
|
+
if runtime.respond_to?(:summary_report, true)
|
57
|
+
runtime.send(:summary_report).test_cases.total_failed
|
58
|
+
else
|
59
|
+
runtime.results.scenarios(:failed).size
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def summarize_worker(worker)
|
64
|
+
output = worker.output.gsub(/\e\[\d+./, '')
|
65
|
+
worker.summary = output.split("\n").grep(/^\d+ (scenarios?|steps?)/).first
|
66
|
+
worker.failure_output = output.scan(/^Failing Scenarios:\n(.*)\n\d+ scenarios?/m).join("\n")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class TestFramework
|
72
|
+
class Cucumber < TestFramework
|
73
|
+
class FakeKernel
|
74
|
+
def exit(n)
|
75
|
+
if $!
|
76
|
+
# Let Cucumber exit for raised exceptions.
|
77
|
+
Kernel.exit(n)
|
78
|
+
end
|
79
|
+
# Don't let Cucumber exit to indicate test failures. We want to
|
80
|
+
# return the number of failures from #run_worker instead.
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def cli
|
85
|
+
@cli ||= ::Cucumber::Cli::Main.new(ARGV.dup, $stdin, $stdout, $stderr, FakeKernel.new)
|
86
|
+
end
|
87
|
+
|
88
|
+
def runtime
|
89
|
+
@runtime ||= ::Cucumber::Runtime.new(cli.configuration)
|
90
|
+
end
|
91
|
+
|
92
|
+
def all_suite_files
|
93
|
+
if runtime.respond_to?(:feature_files, true)
|
94
|
+
runtime.send(:feature_files)
|
95
|
+
else
|
96
|
+
cli.configuration.feature_files
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def suites_from_file(path)
|
101
|
+
if defined?(::Cucumber::Core::Gherkin::Document)
|
102
|
+
source = ::Cucumber::Runtime::NormalisedEncodingFile.read(path)
|
103
|
+
doc = ::Cucumber::Core::Gherkin::Document.new(path, source)
|
104
|
+
[[File.basename(doc.uri), doc]]
|
105
|
+
else
|
106
|
+
loader =
|
107
|
+
::Cucumber::Runtime::FeaturesLoader.new([path],
|
108
|
+
cli.configuration.filters,
|
109
|
+
cli.configuration.tag_expression)
|
110
|
+
loader.features.map { |feature| [feature.title, feature] }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'minitest'
|
3
|
+
require 'test_queue/runner/minitest5'
|
4
|
+
rescue LoadError => e
|
5
|
+
require 'minitest/unit'
|
6
|
+
require 'test_queue/runner/minitest4'
|
7
|
+
end
|
8
|
+
|
9
|
+
module TestQueue
|
10
|
+
class Runner
|
11
|
+
class MiniTest < Runner
|
12
|
+
def summarize_worker(worker)
|
13
|
+
worker.summary = worker.lines.grep(/, \d+ errors?, /).first
|
14
|
+
failures = worker.lines.select{ |line|
|
15
|
+
line if (line =~ /^Finished/) ... (line =~ /, \d+ errors?, /)
|
16
|
+
}[1..-2]
|
17
|
+
worker.failure_output = failures.join("\n") if failures
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'test_queue/runner'
|
2
|
+
require 'set'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
class MiniTestQueueRunner < MiniTest::Unit
|
6
|
+
def _run_suites(suites, type)
|
7
|
+
self.class.output = $stdout
|
8
|
+
|
9
|
+
if defined?(ParallelEach)
|
10
|
+
# Ignore its _run_suites implementation since we don't handle it gracefully.
|
11
|
+
# If we don't do this #partition is called on the iterator and all suites
|
12
|
+
# distributed immediately, instead of picked up as workers are available.
|
13
|
+
suites.map { |suite| _run_suite suite, type }
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def _run_anything(*)
|
20
|
+
ret = super
|
21
|
+
output.puts
|
22
|
+
ret
|
23
|
+
end
|
24
|
+
|
25
|
+
def _run_suite(suite, type)
|
26
|
+
output.print ' '
|
27
|
+
output.print suite
|
28
|
+
output.print ': '
|
29
|
+
|
30
|
+
start = Time.now
|
31
|
+
ret = super
|
32
|
+
diff = Time.now - start
|
33
|
+
|
34
|
+
output.puts(" <%.3f>" % diff)
|
35
|
+
ret
|
36
|
+
end
|
37
|
+
|
38
|
+
self.runner = self.new
|
39
|
+
self.output = StringIO.new
|
40
|
+
end
|
41
|
+
|
42
|
+
class MiniTest::Unit::TestCase
|
43
|
+
class << self
|
44
|
+
attr_accessor :test_suites
|
45
|
+
|
46
|
+
def original_test_suites
|
47
|
+
@@test_suites.keys.reject{ |s| s.test_methods.empty? }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_count
|
52
|
+
failures.length
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module TestQueue
|
57
|
+
class Runner
|
58
|
+
class MiniTest < Runner
|
59
|
+
def initialize
|
60
|
+
if ::MiniTest::Unit::TestCase.original_test_suites.any?
|
61
|
+
fail "Do not `require` test files. Pass them via ARGV instead and they will be required as needed."
|
62
|
+
end
|
63
|
+
super(TestFramework::MiniTest.new)
|
64
|
+
end
|
65
|
+
|
66
|
+
def run_worker(iterator)
|
67
|
+
::MiniTest::Unit::TestCase.test_suites = iterator
|
68
|
+
::MiniTest::Unit.new.run
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class TestFramework
|
74
|
+
class MiniTest < TestFramework
|
75
|
+
def all_suite_files
|
76
|
+
ARGV
|
77
|
+
end
|
78
|
+
|
79
|
+
def suites_from_file(path)
|
80
|
+
::MiniTest::Unit::TestCase.reset
|
81
|
+
require File.absolute_path(path)
|
82
|
+
::MiniTest::Unit::TestCase.original_test_suites.map { |suite|
|
83
|
+
[suite.name, suite]
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'test_queue/runner'
|
2
|
+
|
3
|
+
module MiniTest
|
4
|
+
def self.__run reporter, options
|
5
|
+
suites = Runnable.runnables
|
6
|
+
suites.map { |suite| suite.run reporter, options }
|
7
|
+
end
|
8
|
+
|
9
|
+
class Runnable
|
10
|
+
def failure_count
|
11
|
+
failures.length
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Test
|
16
|
+
def self.runnables= runnables
|
17
|
+
@@runnables = runnables
|
18
|
+
end
|
19
|
+
|
20
|
+
# Synchronize all tests, even serial ones.
|
21
|
+
#
|
22
|
+
# Minitest runs serial tests before parallel ones to ensure the
|
23
|
+
# unsynchronized serial tests don't overlap the parallel tests. But since
|
24
|
+
# the test-queue master hands out tests without actually loading their
|
25
|
+
# code, there's no way to know which are parallel and which are serial.
|
26
|
+
# Synchronizing serial tests does add some overhead, but hopefully this is
|
27
|
+
# outweighed by the speed benefits of using test-queue.
|
28
|
+
def _synchronize; Test.io_lock.synchronize { yield }; end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ProgressReporter
|
32
|
+
# Override original method to make test-queue specific output
|
33
|
+
def record result
|
34
|
+
io.print ' '
|
35
|
+
io.print result.class
|
36
|
+
io.print ': '
|
37
|
+
io.print result.result_code
|
38
|
+
io.puts(" <%.3f>" % result.time)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
require 'minitest/minitest_reporter_plugin'
|
44
|
+
|
45
|
+
class << self
|
46
|
+
private
|
47
|
+
def total_count(options)
|
48
|
+
0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue LoadError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module TestQueue
|
56
|
+
class Runner
|
57
|
+
class MiniTest < Runner
|
58
|
+
def initialize
|
59
|
+
if ::MiniTest::Test.runnables.any? { |r| r.runnable_methods.any? }
|
60
|
+
fail "Do not `require` test files. Pass them via ARGV instead and they will be required as needed."
|
61
|
+
end
|
62
|
+
super(TestFramework::MiniTest.new)
|
63
|
+
end
|
64
|
+
|
65
|
+
def run_worker(iterator)
|
66
|
+
::MiniTest::Test.runnables = iterator
|
67
|
+
::MiniTest.run ? 0 : 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class TestFramework
|
73
|
+
class MiniTest < TestFramework
|
74
|
+
def all_suite_files
|
75
|
+
ARGV
|
76
|
+
end
|
77
|
+
|
78
|
+
def suites_from_file(path)
|
79
|
+
::MiniTest::Test.reset
|
80
|
+
require File.absolute_path(path)
|
81
|
+
::MiniTest::Test.runnables
|
82
|
+
.reject { |s| s.runnable_methods.empty? }
|
83
|
+
.map { |s| [s.name, s] }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_queue'
|
2
|
+
require 'puppet-lint'
|
3
|
+
|
4
|
+
module TestQueue
|
5
|
+
class Runner
|
6
|
+
class PuppetLint < Runner
|
7
|
+
def run_worker(iterator)
|
8
|
+
errors = 0
|
9
|
+
linter = PuppetLint.new
|
10
|
+
iterator.each do |file|
|
11
|
+
puts "Evaluating #{file}"
|
12
|
+
linter.file = file
|
13
|
+
linter.run
|
14
|
+
errors += 1 if linter.errors?
|
15
|
+
end
|
16
|
+
errors
|
17
|
+
end
|
18
|
+
|
19
|
+
def summarize_worker(worker)
|
20
|
+
lines = worker.lines
|
21
|
+
|
22
|
+
files = lines.select{ |line| line =~ /^Evaluating/ }
|
23
|
+
errors = lines.select{ |line| line =~ /^ERROR/ }
|
24
|
+
warnings = lines.select{ |line| line =~ /^WARNING/ }
|
25
|
+
|
26
|
+
worker.summary = "#{files.size} files, #{warnings.size} warnings, #{errors.size} errors"
|
27
|
+
worker.failure_output = errors.join("\n")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_queue/runner'
|
2
|
+
require 'rspec/core'
|
3
|
+
|
4
|
+
case ::RSpec::Core::Version::STRING.to_i
|
5
|
+
when 2
|
6
|
+
require_relative 'rspec2'
|
7
|
+
when 3
|
8
|
+
require_relative 'rspec3'
|
9
|
+
else
|
10
|
+
fail 'requires rspec version 2 or 3'
|
11
|
+
end
|
12
|
+
|
13
|
+
module TestQueue
|
14
|
+
class Runner
|
15
|
+
class RSpec < Runner
|
16
|
+
def initialize
|
17
|
+
super(TestFramework::RSpec.new)
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_worker(iterator)
|
21
|
+
rspec = ::RSpec::Core::QueueRunner.new
|
22
|
+
rspec.run_each(iterator).to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def summarize_worker(worker)
|
26
|
+
worker.summary = worker.lines.grep(/ examples?, /).first
|
27
|
+
worker.failure_output = worker.output[/^Failures:\n\n(.*)\n^Finished/m, 1]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class TestFramework
|
33
|
+
class RSpec < TestFramework
|
34
|
+
def all_suite_files
|
35
|
+
options = ::RSpec::Core::ConfigurationOptions.new(ARGV)
|
36
|
+
options.parse_options if options.respond_to?(:parse_options)
|
37
|
+
options.configure(::RSpec.configuration)
|
38
|
+
|
39
|
+
::RSpec.configuration.files_to_run.uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
def suites_from_file(path)
|
43
|
+
::RSpec.world.example_groups.clear
|
44
|
+
load path
|
45
|
+
split_groups(::RSpec.world.example_groups).map { |example_or_group|
|
46
|
+
name = if example_or_group.respond_to?(:id)
|
47
|
+
example_or_group.id
|
48
|
+
elsif example_or_group.respond_to?(:full_description)
|
49
|
+
example_or_group.full_description
|
50
|
+
elsif example_or_group.metadata.key?(:full_description)
|
51
|
+
example_or_group.metadata[:full_description]
|
52
|
+
else
|
53
|
+
example_or_group.metadata[:example_group][:full_description]
|
54
|
+
end
|
55
|
+
[name, example_or_group]
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def split_groups(groups)
|
62
|
+
return groups unless split_groups?
|
63
|
+
|
64
|
+
groups_to_split, groups_to_keep = [], []
|
65
|
+
groups.each do |group|
|
66
|
+
(group.metadata[:no_split] ? groups_to_keep : groups_to_split) << group
|
67
|
+
end
|
68
|
+
queue = groups_to_split.flat_map(&:descendant_filtered_examples)
|
69
|
+
queue.concat groups_to_keep
|
70
|
+
queue
|
71
|
+
end
|
72
|
+
|
73
|
+
def split_groups?
|
74
|
+
return @split_groups if defined?(@split_groups)
|
75
|
+
@split_groups = ENV['TEST_QUEUE_SPLIT_GROUPS'] && ENV['TEST_QUEUE_SPLIT_GROUPS'].strip.downcase == 'true'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class ::RSpec::Core::ExampleGroup
|
2
|
+
def self.failure_count
|
3
|
+
examples.map {|e| e.execution_result[:status] == "failed"}.length
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module RSpec::Core
|
8
|
+
class QueueRunner < CommandLine
|
9
|
+
def initialize
|
10
|
+
super(ARGV)
|
11
|
+
@configuration.output_stream = $stdout
|
12
|
+
@configuration.error_stream = $stderr
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_each(iterator)
|
16
|
+
@configuration.reporter.report(0, @configuration.randomize? ? @configuration.seed : nil) do |reporter|
|
17
|
+
begin
|
18
|
+
@configuration.run_hook(:before, :suite)
|
19
|
+
iterator.map {|g|
|
20
|
+
if g.is_a? ::RSpec::Core::Example
|
21
|
+
print " #{g.full_description}: "
|
22
|
+
example = g
|
23
|
+
g = example.example_group
|
24
|
+
::RSpec.world.filtered_examples.clear
|
25
|
+
examples = [example]
|
26
|
+
examples.extend(::RSpec::Core::Extensions::Ordered::Examples)
|
27
|
+
::RSpec.world.filtered_examples[g] = examples
|
28
|
+
else
|
29
|
+
print " #{g.description}: "
|
30
|
+
end
|
31
|
+
start = Time.now
|
32
|
+
ret = g.run(reporter)
|
33
|
+
diff = Time.now-start
|
34
|
+
puts(" <%.3f>" % diff)
|
35
|
+
|
36
|
+
ret
|
37
|
+
}.all? ? 0 : @configuration.failure_exit_code
|
38
|
+
ensure
|
39
|
+
@configuration.run_hook(:after, :suite)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class ::RSpec::Core::ExampleGroup
|
2
|
+
def self.failure_count
|
3
|
+
examples.map {|e| e.execution_result.status == "failed"}.length
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module RSpec::Core
|
8
|
+
# RSpec 3.2 introduced:
|
9
|
+
unless Configuration.method_defined?(:with_suite_hooks)
|
10
|
+
class Configuration
|
11
|
+
def with_suite_hooks
|
12
|
+
begin
|
13
|
+
hook_context = SuiteHookContext.new
|
14
|
+
hooks.run(:before, :suite, hook_context)
|
15
|
+
yield
|
16
|
+
ensure
|
17
|
+
hooks.run(:after, :suite, hook_context)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class QueueRunner < Runner
|
24
|
+
def initialize
|
25
|
+
options = ConfigurationOptions.new(ARGV)
|
26
|
+
super(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def run_specs(iterator)
|
30
|
+
@configuration.reporter.report(0) do |reporter|
|
31
|
+
@configuration.with_suite_hooks do
|
32
|
+
iterator.map { |g|
|
33
|
+
start = Time.now
|
34
|
+
if g.is_a? ::RSpec::Core::Example
|
35
|
+
print " #{g.full_description}: "
|
36
|
+
example = g
|
37
|
+
g = example.example_group
|
38
|
+
::RSpec.world.filtered_examples.clear
|
39
|
+
::RSpec.world.filtered_examples[g] = [example]
|
40
|
+
else
|
41
|
+
print " #{g.description}: "
|
42
|
+
end
|
43
|
+
ret = g.run(reporter)
|
44
|
+
diff = Time.now-start
|
45
|
+
puts(" <%.3f>" % diff)
|
46
|
+
|
47
|
+
ret
|
48
|
+
}.all? ? 0 : @configuration.failure_exit_code
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
alias_method :run_each, :run_specs
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'test_queue'
|
2
|
+
require 'test_queue/runner'
|
3
|
+
|
4
|
+
module TestQueue
|
5
|
+
class Runner
|
6
|
+
class Sample < Runner
|
7
|
+
def spawn_workers
|
8
|
+
puts "Spawning #@concurrency workers"
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def after_fork(num)
|
13
|
+
puts " -- worker #{num} booted as pid #{$$}"
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_worker(iterator)
|
18
|
+
sum = 0
|
19
|
+
iterator.each do |item|
|
20
|
+
puts " #{item.inspect}"
|
21
|
+
sum += item
|
22
|
+
end
|
23
|
+
sum
|
24
|
+
end
|
25
|
+
|
26
|
+
def summarize_worker(worker)
|
27
|
+
worker.summary = worker.output.scan(/^\s*(\d+)/).join(', ')
|
28
|
+
worker.failure_output = ''
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if __FILE__ == $0
|
35
|
+
TestQueue::Runner::Sample.new(Array(1..10)).execute
|
36
|
+
end
|
37
|
+
|
38
|
+
__END__
|
39
|
+
|
40
|
+
Spawning 4 workers
|
41
|
+
-- worker 0 booted as pid 40406
|
42
|
+
-- worker 1 booted as pid 40407
|
43
|
+
-- worker 2 booted as pid 40408
|
44
|
+
-- worker 3 booted as pid 40409
|
45
|
+
|
46
|
+
==> Starting ruby test-queue worker [1] (40407)
|
47
|
+
|
48
|
+
2
|
49
|
+
5
|
50
|
+
8
|
51
|
+
|
52
|
+
==> Starting ruby test-queue worker [3] (40409)
|
53
|
+
|
54
|
+
|
55
|
+
==> Starting ruby test-queue worker [2] (40408)
|
56
|
+
|
57
|
+
3
|
58
|
+
6
|
59
|
+
9
|
60
|
+
|
61
|
+
==> Starting ruby test-queue worker [0] (40406)
|
62
|
+
|
63
|
+
1
|
64
|
+
4
|
65
|
+
7
|
66
|
+
10
|
67
|
+
|
68
|
+
==> Summary
|
69
|
+
|
70
|
+
[1] 2, 5, 8 in 0.0024s (pid 40407 exit 15)
|
71
|
+
[3] in 0.0036s (pid 40409 exit 0)
|
72
|
+
[2] 3, 6, 9 in 0.0038s (pid 40408 exit 18)
|
73
|
+
[0] 1, 4, 7, 10 in 0.0044s (pid 40406 exit 22)
|
74
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'test_queue/runner'
|
2
|
+
|
3
|
+
gem 'test-unit'
|
4
|
+
require 'test/unit'
|
5
|
+
require 'test/unit/collector/descendant'
|
6
|
+
require 'test/unit/testresult'
|
7
|
+
require 'test/unit/testsuite'
|
8
|
+
require 'test/unit/ui/console/testrunner'
|
9
|
+
|
10
|
+
class Test::Unit::TestSuite
|
11
|
+
attr_accessor :iterator
|
12
|
+
|
13
|
+
def run(result, &progress_block)
|
14
|
+
@start_time = Time.now
|
15
|
+
yield(STARTED, name)
|
16
|
+
yield(STARTED_OBJECT, self)
|
17
|
+
run_startup(result)
|
18
|
+
(@iterator || @tests).each do |test|
|
19
|
+
@n_tests += test.size
|
20
|
+
run_test(test, result, &progress_block)
|
21
|
+
@passed = false unless test.passed?
|
22
|
+
end
|
23
|
+
run_shutdown(result)
|
24
|
+
ensure
|
25
|
+
@elapsed_time = Time.now - @start_time
|
26
|
+
yield(FINISHED, name)
|
27
|
+
yield(FINISHED_OBJECT, self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def failure_count
|
31
|
+
(@iterator || @tests).map {|t| t.instance_variable_get(:@_result).failure_count}.inject(0, :+)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module TestQueue
|
36
|
+
class Runner
|
37
|
+
class TestUnit < Runner
|
38
|
+
def initialize
|
39
|
+
if Test::Unit::Collector::Descendant.new.collect.tests.any?
|
40
|
+
fail "Do not `require` test files. Pass them via ARGV instead and they will be required as needed."
|
41
|
+
end
|
42
|
+
super(TestFramework::TestUnit.new)
|
43
|
+
end
|
44
|
+
|
45
|
+
def run_worker(iterator)
|
46
|
+
@suite = Test::Unit::TestSuite.new("specified by test-queue master")
|
47
|
+
@suite.iterator = iterator
|
48
|
+
res = Test::Unit::UI::Console::TestRunner.new(@suite).start
|
49
|
+
res.run_count - res.pass_count
|
50
|
+
end
|
51
|
+
|
52
|
+
def summarize_worker(worker)
|
53
|
+
worker.summary = worker.output.split("\n").grep(/^\d+ tests?/).first
|
54
|
+
worker.failure_output = worker.output.scan(/^Failure:\n(.*)\n=======================*/m).join("\n")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class TestFramework
|
60
|
+
class TestUnit < TestFramework
|
61
|
+
def all_suite_files
|
62
|
+
ARGV
|
63
|
+
end
|
64
|
+
|
65
|
+
def suites_from_file(path)
|
66
|
+
Test::Unit::TestCase::DESCENDANTS.clear
|
67
|
+
require File.absolute_path(path)
|
68
|
+
Test::Unit::Collector::Descendant.new.collect.tests.map { |suite|
|
69
|
+
[suite.name, suite]
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|