causes-hydra 0.21.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.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +56 -0
- data/TODO +18 -0
- data/VERSION +1 -0
- data/bin/warmsnake.rb +76 -0
- data/caliper.yml +6 -0
- data/hydra-icon-64x64.png +0 -0
- data/hydra.gemspec +130 -0
- data/hydra_gray.png +0 -0
- data/lib/hydra.rb +16 -0
- data/lib/hydra/cucumber/formatter.rb +29 -0
- data/lib/hydra/hash.rb +16 -0
- data/lib/hydra/js/lint.js +5150 -0
- data/lib/hydra/listener/abstract.rb +39 -0
- data/lib/hydra/listener/minimal_output.rb +24 -0
- data/lib/hydra/listener/notifier.rb +17 -0
- data/lib/hydra/listener/progress_bar.rb +48 -0
- data/lib/hydra/listener/report_generator.rb +30 -0
- data/lib/hydra/master.rb +249 -0
- data/lib/hydra/message.rb +47 -0
- data/lib/hydra/message/master_messages.rb +19 -0
- data/lib/hydra/message/runner_messages.rb +52 -0
- data/lib/hydra/message/worker_messages.rb +52 -0
- data/lib/hydra/messaging_io.rb +46 -0
- data/lib/hydra/pipe.rb +61 -0
- data/lib/hydra/runner.rb +305 -0
- data/lib/hydra/safe_fork.rb +31 -0
- data/lib/hydra/spec/autorun_override.rb +3 -0
- data/lib/hydra/spec/hydra_formatter.rb +26 -0
- data/lib/hydra/ssh.rb +41 -0
- data/lib/hydra/stdio.rb +16 -0
- data/lib/hydra/sync.rb +99 -0
- data/lib/hydra/tasks.rb +342 -0
- data/lib/hydra/trace.rb +24 -0
- data/lib/hydra/worker.rb +150 -0
- data/test/fixtures/assert_true.rb +7 -0
- data/test/fixtures/config.yml +4 -0
- data/test/fixtures/features/step_definitions.rb +21 -0
- data/test/fixtures/features/write_alternate_file.feature +7 -0
- data/test/fixtures/features/write_file.feature +7 -0
- data/test/fixtures/hello_world.rb +3 -0
- data/test/fixtures/js_file.js +4 -0
- data/test/fixtures/json_data.json +4 -0
- data/test/fixtures/slow.rb +9 -0
- data/test/fixtures/sync_test.rb +8 -0
- data/test/fixtures/write_file.rb +10 -0
- data/test/fixtures/write_file_alternate_spec.rb +10 -0
- data/test/fixtures/write_file_spec.rb +9 -0
- data/test/fixtures/write_file_with_pending_spec.rb +11 -0
- data/test/master_test.rb +152 -0
- data/test/message_test.rb +31 -0
- data/test/pipe_test.rb +38 -0
- data/test/runner_test.rb +153 -0
- data/test/ssh_test.rb +14 -0
- data/test/sync_test.rb +113 -0
- data/test/test_helper.rb +68 -0
- data/test/worker_test.rb +60 -0
- metadata +209 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
module Messages #:nodoc:
|
3
|
+
module Master #:nodoc:
|
4
|
+
# Message telling a worker to delegate a file to a runner
|
5
|
+
class RunFile < Hydra::Messages::Worker::RunFile
|
6
|
+
def handle(worker) #:nodoc:
|
7
|
+
worker.delegate_file(self)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Message telling the worker to shut down.
|
12
|
+
class Shutdown < Hydra::Messages::Worker::Shutdown
|
13
|
+
def handle(worker) #:nodoc:
|
14
|
+
worker.shutdown
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
module Messages #:nodoc:
|
3
|
+
module Runner #:nodoc:
|
4
|
+
# Message indicating that a Runner needs a file to run
|
5
|
+
class RequestFile < Hydra::Message
|
6
|
+
def handle(worker, runner) #:nodoc:
|
7
|
+
worker.request_file(self, runner)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Message for the Runner to respond with its results
|
12
|
+
class Results < Hydra::Message
|
13
|
+
# The output from running the test
|
14
|
+
attr_accessor :output
|
15
|
+
# The file that was run
|
16
|
+
attr_accessor :file
|
17
|
+
# Stats on the number of tests, assertions, errors and failures
|
18
|
+
attr_accessor :stats
|
19
|
+
def serialize #:nodoc:
|
20
|
+
super(
|
21
|
+
:output => @output,
|
22
|
+
:file => @file,
|
23
|
+
:stats => @stats
|
24
|
+
)
|
25
|
+
end
|
26
|
+
def handle(worker, runner) #:nodoc:
|
27
|
+
worker.relay_results(self, runner)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Message a runner sends to a worker to verify the connection
|
32
|
+
class Ping < Hydra::Message
|
33
|
+
def handle(worker, runner) #:nodoc:
|
34
|
+
# We don't do anything to handle a ping. It's just to test
|
35
|
+
# the connectivity of the IO
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# The runner forks to run rspec messages
|
40
|
+
# so that specs don't get rerun. It uses
|
41
|
+
# this message to report the results. See
|
42
|
+
# Runner::run_rspec_file.
|
43
|
+
class RSpecResult < Hydra::Message
|
44
|
+
# the output of the spec
|
45
|
+
attr_accessor :output
|
46
|
+
def serialize #:nodoc:
|
47
|
+
super(:output => @output)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
module Messages #:nodoc:
|
3
|
+
module Worker #:nodoc:
|
4
|
+
# Message indicating that a worker needs a file to delegate to a runner
|
5
|
+
class RequestFile < Hydra::Message
|
6
|
+
def handle(master, worker) #:nodoc:
|
7
|
+
master.send_file(worker)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class WorkerBegin < Hydra::Message
|
12
|
+
def handle(master, worker)
|
13
|
+
master.worker_begin(worker)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Message telling the Runner to run a file
|
18
|
+
class RunFile < Hydra::Message
|
19
|
+
# The file that should be run
|
20
|
+
attr_accessor :file
|
21
|
+
def serialize #:nodoc:
|
22
|
+
super(:file => @file)
|
23
|
+
end
|
24
|
+
def handle(runner) #:nodoc:
|
25
|
+
runner.run_file(@file)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Message to tell the Runner to shut down
|
30
|
+
class Shutdown < Hydra::Message
|
31
|
+
def handle(runner) #:nodoc:
|
32
|
+
runner.stop
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Message relaying the results of a worker up to the master
|
37
|
+
class Results < Hydra::Messages::Runner::Results
|
38
|
+
def handle(master, worker) #:nodoc:
|
39
|
+
master.process_results(worker, self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Message a worker sends to a master to verify the connection
|
44
|
+
class Ping < Hydra::Message
|
45
|
+
def handle(master, worker) #:nodoc:
|
46
|
+
# We don't do anything to handle a ping. It's just to test
|
47
|
+
# the connectivity of the IO
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Module that implemets methods that auto-serialize and deserialize messaging
|
3
|
+
# objects.
|
4
|
+
module MessagingIO
|
5
|
+
# Read a Message from the input IO object. Automatically build
|
6
|
+
# a message from the response and return it.
|
7
|
+
#
|
8
|
+
# IO.gets
|
9
|
+
# => Hydra::Message # or subclass
|
10
|
+
def gets
|
11
|
+
raise IOError unless @reader
|
12
|
+
message = @reader.gets
|
13
|
+
return nil unless message
|
14
|
+
return Message.build(eval(message.chomp))
|
15
|
+
rescue SyntaxError, NameError
|
16
|
+
# uncomment to help catch remote errors by seeing all traffic
|
17
|
+
#$stderr.write "Not a message: [#{message.inspect}]\n"
|
18
|
+
return gets
|
19
|
+
end
|
20
|
+
|
21
|
+
# Write a Message to the output IO object. It will automatically
|
22
|
+
# serialize a Message object.
|
23
|
+
# IO.write Hydra::Message.new
|
24
|
+
def write(message)
|
25
|
+
raise IOError unless @writer
|
26
|
+
raise UnprocessableMessage unless message.is_a?(Hydra::Message)
|
27
|
+
@writer.write(message.serialize+"\n")
|
28
|
+
rescue Errno::EPIPE
|
29
|
+
raise IOError
|
30
|
+
end
|
31
|
+
|
32
|
+
# Closes the IO object.
|
33
|
+
def close
|
34
|
+
@reader.close if @reader
|
35
|
+
@writer.close if @writer
|
36
|
+
end
|
37
|
+
|
38
|
+
# IO will return this error if it cannot process a message.
|
39
|
+
# For example, if you tried to write a string, it would fail,
|
40
|
+
# because the string is not a message.
|
41
|
+
class UnprocessableMessage < RuntimeError
|
42
|
+
# Custom error message
|
43
|
+
attr_accessor :message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/hydra/pipe.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'hydra/messaging_io'
|
2
|
+
module Hydra #:nodoc:
|
3
|
+
# Read and write between two processes via pipes. For example:
|
4
|
+
# @pipe = Hydra::Pipe.new
|
5
|
+
# @child = Process.fork do
|
6
|
+
# @pipe.identify_as_child
|
7
|
+
# puts "A message from my parent:\n#{@pipe.gets.text}"
|
8
|
+
# @pipe.close
|
9
|
+
# end
|
10
|
+
# @pipe.identify_as_parent
|
11
|
+
# @pipe.write Hydra::Messages::TestMessage.new(:text => "Hello!")
|
12
|
+
# @pipe.close
|
13
|
+
#
|
14
|
+
# Note that the TestMessage class is only available in tests, and
|
15
|
+
# not in Hydra by default.
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# When the process forks, the pipe is copied. When a pipe is
|
19
|
+
# identified as a parent or child, it is choosing which ends
|
20
|
+
# of the pipe to use.
|
21
|
+
#
|
22
|
+
# A pipe is actually two pipes:
|
23
|
+
#
|
24
|
+
# Parent == Pipe 1 ==> Child
|
25
|
+
# Parent <== Pipe 2 == Child
|
26
|
+
#
|
27
|
+
# It's like if you had two cardboard tubes and you were using
|
28
|
+
# them to drop balls with messages in them between processes.
|
29
|
+
# One tube is for sending from parent to child, and the other
|
30
|
+
# tube is for sending from child to parent.
|
31
|
+
class Pipe
|
32
|
+
include Hydra::MessagingIO
|
33
|
+
# Creates a new uninitialized pipe pair.
|
34
|
+
def initialize
|
35
|
+
@child_read, @parent_write = IO.pipe
|
36
|
+
@parent_read, @child_write = IO.pipe
|
37
|
+
end
|
38
|
+
|
39
|
+
# Identify this side of the pipe as the child.
|
40
|
+
def identify_as_child
|
41
|
+
@parent_write.close
|
42
|
+
@parent_read.close
|
43
|
+
@reader = @child_read
|
44
|
+
@writer = @child_write
|
45
|
+
end
|
46
|
+
|
47
|
+
# Identify this side of the pipe as the parent
|
48
|
+
def identify_as_parent
|
49
|
+
@child_write.close
|
50
|
+
@child_read.close
|
51
|
+
@reader = @parent_read
|
52
|
+
@writer = @parent_write
|
53
|
+
end
|
54
|
+
|
55
|
+
# Output pipe nicely
|
56
|
+
def inspect
|
57
|
+
"#<#{self.class} @reader=#{@reader.to_s}, @writer=#{@writer.to_s}>"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/hydra/runner.rb
ADDED
@@ -0,0 +1,305 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/testresult'
|
3
|
+
Test::Unit.run = true
|
4
|
+
|
5
|
+
module Hydra #:nodoc:
|
6
|
+
# Hydra class responsible for running test files.
|
7
|
+
#
|
8
|
+
# The Runner is never run directly by a user. Runners are created by a
|
9
|
+
# Worker to run test files.
|
10
|
+
#
|
11
|
+
# The general convention is to have one Runner for each logical processor
|
12
|
+
# of a machine.
|
13
|
+
class Runner
|
14
|
+
include Hydra::Messages::Runner
|
15
|
+
traceable('RUNNER')
|
16
|
+
# Boot up a runner. It takes an IO object (generally a pipe from its
|
17
|
+
# parent) to send it messages on which files to execute.
|
18
|
+
def initialize(opts = {})
|
19
|
+
@io = opts.fetch(:io) { raise "No IO Object" }
|
20
|
+
@verbose = opts.fetch(:verbose) { false }
|
21
|
+
$stdout.sync = true
|
22
|
+
trace 'Booted. Sending Request for file'
|
23
|
+
|
24
|
+
@io.write RequestFile.new
|
25
|
+
begin
|
26
|
+
process_messages
|
27
|
+
rescue => ex
|
28
|
+
trace ex.to_s
|
29
|
+
raise ex
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Run a test file and report the results
|
34
|
+
def run_file(file)
|
35
|
+
trace "Running file: #{file}"
|
36
|
+
|
37
|
+
output = ""
|
38
|
+
if file =~ /_spec.rb$/i
|
39
|
+
output, stats = run_rspec_file(file)
|
40
|
+
elsif file =~ /_testspec.rb$/i
|
41
|
+
output, stats = run_test_spec_file(file)
|
42
|
+
elsif file =~ /.feature$/i
|
43
|
+
output, stats = run_cucumber_file(file)
|
44
|
+
elsif file =~ /.js$/i or file =~ /.json$/i
|
45
|
+
output, stats = run_javascript_file(file)
|
46
|
+
else
|
47
|
+
output, stats = run_test_unit_file(file)
|
48
|
+
end
|
49
|
+
|
50
|
+
output = "." if output == ""
|
51
|
+
|
52
|
+
@io.write Results.new(
|
53
|
+
:output => output,
|
54
|
+
:file => file,
|
55
|
+
:stats => stats
|
56
|
+
)
|
57
|
+
return output
|
58
|
+
end
|
59
|
+
|
60
|
+
# Stop running
|
61
|
+
def stop
|
62
|
+
@running = false
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# The runner will continually read messages and handle them.
|
68
|
+
def process_messages
|
69
|
+
trace "Processing Messages"
|
70
|
+
@running = true
|
71
|
+
while @running
|
72
|
+
begin
|
73
|
+
message = @io.gets
|
74
|
+
if message and !message.class.to_s.index("Worker").nil?
|
75
|
+
trace "Received message from worker"
|
76
|
+
trace "\t#{message.inspect}"
|
77
|
+
message.handle(self)
|
78
|
+
else
|
79
|
+
@io.write Ping.new
|
80
|
+
end
|
81
|
+
rescue IOError => ex
|
82
|
+
trace "Runner lost Worker"
|
83
|
+
@running = false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Run all the Test::Unit Suites in a ruby file
|
89
|
+
def run_test_unit_file(file)
|
90
|
+
begin
|
91
|
+
require file
|
92
|
+
rescue LoadError => ex
|
93
|
+
trace "#{file} does not exist [#{ex.to_s}]"
|
94
|
+
return ex.to_s
|
95
|
+
end
|
96
|
+
output = []
|
97
|
+
@result = Test::Unit::TestResult.new
|
98
|
+
@result.add_listener(Test::Unit::TestResult::FAULT) do |value|
|
99
|
+
output << `hostname`.chomp
|
100
|
+
output << value
|
101
|
+
end
|
102
|
+
|
103
|
+
klasses = Runner.find_classes_in_file(file)
|
104
|
+
begin
|
105
|
+
klasses.each{|klass| klass.suite.run(@result){|status, name| ;}}
|
106
|
+
rescue => ex
|
107
|
+
output << `hostname`.chomp
|
108
|
+
output << ex.to_s
|
109
|
+
end
|
110
|
+
stats = {
|
111
|
+
:tests => @result.run_count,
|
112
|
+
:assertions => @result.assertion_count,
|
113
|
+
:failures => @result.failure_count,
|
114
|
+
:errors => @result.error_count,
|
115
|
+
}
|
116
|
+
|
117
|
+
return output.join("\n"), stats
|
118
|
+
end
|
119
|
+
|
120
|
+
# Run all the test/spec file
|
121
|
+
def run_test_spec_file(file)
|
122
|
+
begin
|
123
|
+
require file
|
124
|
+
rescue LoadError => ex
|
125
|
+
trace "#{file} does not exist [#{ex.to_s}]"
|
126
|
+
return ex.to_s
|
127
|
+
end
|
128
|
+
output = []
|
129
|
+
@result = Test::Unit::TestResult.new
|
130
|
+
@result.add_listener(Test::Unit::TestResult::FAULT) do |value|
|
131
|
+
output << `hostname`.chomp
|
132
|
+
output << value
|
133
|
+
end
|
134
|
+
|
135
|
+
klasses = Runner.find_test_spec_classes_in_file(file)
|
136
|
+
begin
|
137
|
+
klasses.each{|klass| klass.suite.run(@result){|status, name| ;}}
|
138
|
+
rescue => ex
|
139
|
+
output << `hostname`.chomp
|
140
|
+
output << ex.to_s
|
141
|
+
end
|
142
|
+
|
143
|
+
stats = {
|
144
|
+
:tests => @result.run_count,
|
145
|
+
:assertions => @result.assertion_count,
|
146
|
+
:failures => @result.failure_count,
|
147
|
+
:errors => @result.error_count,
|
148
|
+
}
|
149
|
+
|
150
|
+
return output.join("\n"), stats
|
151
|
+
end
|
152
|
+
|
153
|
+
# run all the Specs in an RSpec file (NOT IMPLEMENTED)
|
154
|
+
def run_rspec_file(file)
|
155
|
+
# pull in rspec
|
156
|
+
begin
|
157
|
+
require 'rspec'
|
158
|
+
require 'hydra/spec/hydra_formatter'
|
159
|
+
# Ensure we override rspec's at_exit
|
160
|
+
require 'hydra/spec/autorun_override'
|
161
|
+
rescue LoadError => ex
|
162
|
+
return ex.to_s
|
163
|
+
end
|
164
|
+
hydra_output = StringIO.new
|
165
|
+
|
166
|
+
config = [
|
167
|
+
'-f', 'RSpec::Core::Formatters::HydraFormatter',
|
168
|
+
file
|
169
|
+
]
|
170
|
+
|
171
|
+
RSpec.instance_variable_set(:@world, nil)
|
172
|
+
RSpec::Core::Runner.run(config, hydra_output, hydra_output)
|
173
|
+
|
174
|
+
hydra_output.rewind
|
175
|
+
output = hydra_output.read.chomp
|
176
|
+
output = "" if output.gsub("\n","") =~ /^\.*$/
|
177
|
+
|
178
|
+
return output
|
179
|
+
end
|
180
|
+
|
181
|
+
# run all the scenarios in a cucumber feature file
|
182
|
+
def run_cucumber_file(file)
|
183
|
+
|
184
|
+
files = [file]
|
185
|
+
dev_null = StringIO.new
|
186
|
+
hydra_response = StringIO.new
|
187
|
+
|
188
|
+
unless @step_mother
|
189
|
+
require 'cucumber'
|
190
|
+
require 'hydra/cucumber/formatter'
|
191
|
+
@step_mother = Cucumber::StepMother.new
|
192
|
+
@cuke_configuration = Cucumber::Cli::Configuration.new(dev_null, dev_null)
|
193
|
+
@cuke_configuration.parse!(['features']+files)
|
194
|
+
|
195
|
+
@step_mother.options = @cuke_configuration.options
|
196
|
+
@step_mother.log = @cuke_configuration.log
|
197
|
+
@step_mother.load_code_files(@cuke_configuration.support_to_load)
|
198
|
+
@step_mother.after_configuration(@cuke_configuration)
|
199
|
+
@step_mother.load_code_files(@cuke_configuration.step_defs_to_load)
|
200
|
+
end
|
201
|
+
cuke_formatter = Cucumber::Formatter::Hydra.new(
|
202
|
+
@step_mother, hydra_response, @cuke_configuration.options
|
203
|
+
)
|
204
|
+
|
205
|
+
cuke_runner ||= Cucumber::Ast::TreeWalker.new(
|
206
|
+
@step_mother, [cuke_formatter], @cuke_configuration.options, dev_null
|
207
|
+
)
|
208
|
+
@step_mother.visitor = cuke_runner
|
209
|
+
|
210
|
+
features = @step_mother.load_plain_text_features(files)
|
211
|
+
tag_excess = tag_excess(features, @cuke_configuration.options[:tag_expression].limits)
|
212
|
+
@cuke_configuration.options[:tag_excess] = tag_excess
|
213
|
+
|
214
|
+
cuke_runner.visit_features(features)
|
215
|
+
|
216
|
+
hydra_response.rewind
|
217
|
+
return hydra_response.read
|
218
|
+
end
|
219
|
+
|
220
|
+
def run_javascript_file(file)
|
221
|
+
errors = []
|
222
|
+
require 'v8'
|
223
|
+
V8::Context.new do |context|
|
224
|
+
context.load(File.expand_path(File.join(File.dirname(__FILE__), 'js', 'lint.js')))
|
225
|
+
context['input'] = lambda{
|
226
|
+
File.read(file)
|
227
|
+
}
|
228
|
+
context['reportErrors'] = lambda{|js_errors|
|
229
|
+
js_errors.each do |e|
|
230
|
+
e = V8::To.rb(e)
|
231
|
+
errors << "\n\e[1;31mJSLINT: #{file}\e[0m"
|
232
|
+
errors << " Error at line #{e['line'].to_i + 1} " +
|
233
|
+
"character #{e['character'].to_i + 1}: \e[1;33m#{e['reason']}\e[0m"
|
234
|
+
errors << "#{e['evidence']}"
|
235
|
+
end
|
236
|
+
}
|
237
|
+
context.eval %{
|
238
|
+
JSLINT(input(), {
|
239
|
+
sub: true,
|
240
|
+
onevar: true,
|
241
|
+
eqeqeq: true,
|
242
|
+
plusplus: true,
|
243
|
+
bitwise: true,
|
244
|
+
regexp: true,
|
245
|
+
newcap: true,
|
246
|
+
immed: true,
|
247
|
+
strict: true,
|
248
|
+
rhino: true
|
249
|
+
});
|
250
|
+
reportErrors(JSLINT.errors);
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
if errors.empty?
|
255
|
+
return '.'
|
256
|
+
else
|
257
|
+
return errors.join("\n")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.find_test_spec_classes_in_file(f)
|
262
|
+
require f
|
263
|
+
ks = Test::Spec::CONTEXTS.values.map{|k| k.testcase}
|
264
|
+
Test::Spec::CONTEXTS.clear
|
265
|
+
Test::Spec::SHARED_CONTEXTS.clear
|
266
|
+
ks
|
267
|
+
end
|
268
|
+
|
269
|
+
# find all the test unit classes in a given file, so we can run their suites
|
270
|
+
def self.find_classes_in_file(f)
|
271
|
+
code = ""
|
272
|
+
File.open(f) {|buffer| code = buffer.read}
|
273
|
+
matches = code.scan(/class\s+([\S]+)/)
|
274
|
+
klasses = matches.collect do |c|
|
275
|
+
begin
|
276
|
+
if c.first.respond_to? :constantize
|
277
|
+
c.first.constantize
|
278
|
+
else
|
279
|
+
eval(c.first)
|
280
|
+
end
|
281
|
+
rescue NameError
|
282
|
+
# means we could not load [c.first], but thats ok, its just not
|
283
|
+
# one of the classes we want to test
|
284
|
+
nil
|
285
|
+
rescue SyntaxError
|
286
|
+
# see above
|
287
|
+
nil
|
288
|
+
end
|
289
|
+
end
|
290
|
+
return klasses.select{|k| k.respond_to? 'suite'}
|
291
|
+
end
|
292
|
+
|
293
|
+
# Yanked a method from Cucumber
|
294
|
+
def tag_excess(features, limits)
|
295
|
+
limits.map do |tag_name, tag_limit|
|
296
|
+
tag_locations = features.tag_locations(tag_name)
|
297
|
+
if tag_limit && (tag_locations.length > tag_limit)
|
298
|
+
[tag_name, tag_limit, tag_locations]
|
299
|
+
else
|
300
|
+
nil
|
301
|
+
end
|
302
|
+
end.compact
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|