test-queue-patched 0.4.3
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.
- 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
|