test-queue-patched 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +18 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile-cucumber1-3 +4 -0
  6. data/Gemfile-cucumber1-3.lock +33 -0
  7. data/Gemfile-cucumber2-4 +4 -0
  8. data/Gemfile-cucumber2-4.lock +37 -0
  9. data/Gemfile-minitest4 +3 -0
  10. data/Gemfile-minitest4.lock +19 -0
  11. data/Gemfile-minitest5 +3 -0
  12. data/Gemfile-minitest5.lock +19 -0
  13. data/Gemfile-rspec2-1 +3 -0
  14. data/Gemfile-rspec2-1.lock +27 -0
  15. data/Gemfile-rspec3-0 +3 -0
  16. data/Gemfile-rspec3-0.lock +31 -0
  17. data/Gemfile-rspec3-1 +3 -0
  18. data/Gemfile-rspec3-1.lock +31 -0
  19. data/Gemfile-rspec3-2 +3 -0
  20. data/Gemfile-rspec3-2.lock +32 -0
  21. data/Gemfile-testunit +3 -0
  22. data/Gemfile-testunit.lock +21 -0
  23. data/Gemfile.lock +41 -0
  24. data/README.md +126 -0
  25. data/bin/cucumber-queue +4 -0
  26. data/bin/minitest-queue +4 -0
  27. data/bin/rspec-queue +4 -0
  28. data/bin/testunit-queue +4 -0
  29. data/lib/test-queue.rb +1 -0
  30. data/lib/test_queue/iterator.rb +107 -0
  31. data/lib/test_queue/runner/cucumber.rb +115 -0
  32. data/lib/test_queue/runner/minitest.rb +21 -0
  33. data/lib/test_queue/runner/minitest4.rb +88 -0
  34. data/lib/test_queue/runner/minitest5.rb +87 -0
  35. data/lib/test_queue/runner/puppet_lint.rb +31 -0
  36. data/lib/test_queue/runner/rspec.rb +79 -0
  37. data/lib/test_queue/runner/rspec2.rb +44 -0
  38. data/lib/test_queue/runner/rspec3.rb +54 -0
  39. data/lib/test_queue/runner/sample.rb +74 -0
  40. data/lib/test_queue/runner/testunit.rb +74 -0
  41. data/lib/test_queue/runner.rb +632 -0
  42. data/lib/test_queue/stats.rb +95 -0
  43. data/lib/test_queue/test_framework.rb +29 -0
  44. data/lib/test_queue.rb +8 -0
  45. data/script/bootstrap +12 -0
  46. data/script/cibuild +19 -0
  47. data/script/spec +7 -0
  48. data/spec/stats_spec.rb +76 -0
  49. data/test/cucumber.bats +57 -0
  50. data/test/minitest4.bats +34 -0
  51. data/test/minitest5.bats +194 -0
  52. data/test/rspec.bats +46 -0
  53. data/test/samples/features/bad.feature +5 -0
  54. data/test/samples/features/sample.feature +25 -0
  55. data/test/samples/features/sample2.feature +29 -0
  56. data/test/samples/features/step_definitions/common.rb +19 -0
  57. data/test/samples/sample_minispec.rb +37 -0
  58. data/test/samples/sample_minitest4.rb +25 -0
  59. data/test/samples/sample_minitest5.rb +33 -0
  60. data/test/samples/sample_rspec_helper.rb +1 -0
  61. data/test/samples/sample_shared_examples_for_spec.rb +5 -0
  62. data/test/samples/sample_spec.rb +25 -0
  63. data/test/samples/sample_split_spec.rb +17 -0
  64. data/test/samples/sample_testunit.rb +25 -0
  65. data/test/samples/sample_use_shared_example1_spec.rb +8 -0
  66. data/test/samples/sample_use_shared_example2_spec.rb +8 -0
  67. data/test/sleepy_runner.rb +14 -0
  68. data/test/testlib.bash +89 -0
  69. data/test/testunit.bats +20 -0
  70. data/test-queue-patched.gemspec +21 -0
  71. 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