| require 'socket' | |
| begin | |
| # ssanders: Try to load RSpec based on the LOADPATH, this allows | |
| # projects (e.g. Rails) to provide alternate versions via the buildpath | |
| require 'spec' | |
| rescue LoadError | |
| # ssanders: Fallback to loading from the Gem | |
| require 'rubygems' | |
| gem 'rspec' | |
| require 'spec' | |
| end | |
| require 'spec/runner/formatter/base_formatter' | |
| module Spec | |
| module Example | |
| class ExampleGroup | |
| alias_method :initialize_old, :initialize | |
| def initialize(*args, &block) | |
| result = initialize_old(*args, &block) | |
| # ssanders: Override for "pending" examples | |
| unless block | |
| @from = caller | |
| while !@from.empty? && /.*`it'/ !~ @from.first | |
| @from.shift | |
| end | |
| @from.shift | |
| end | |
| result | |
| end | |
| def implementation_backtrace | |
| if @from | |
| @from | |
| else | |
| super | |
| end | |
| end | |
| end | |
| module ExampleMethods | |
| IN_METHOD_RE = /^(.+):in `(.+)'$/ | |
| def rspecTestName | |
| if @DLTK_backtrace.nil? | |
| # tgrimm: This a workaround for a bug in 1.1.12: | |
| filtered_backtrace = respond_to?(:backtrace) ? backtrace : implementation_backtrace | |
| filtered_backtrace = filtered_backtrace.reject { |bt| bt =~ /(example_group_methods|dltk-rspec-runner.rb)/ } | |
| @DLTK_backtrace = filtered_backtrace[0] | |
| if @DLTK_backtrace =~ IN_METHOD_RE | |
| @DLTK_backtrace = $1 | |
| end | |
| end | |
| description + '<' + @DLTK_backtrace | |
| end | |
| end | |
| end | |
| end | |
| module Spec | |
| module Example | |
| module ExampleGroupMethods | |
| def DLTK_examples_to_run | |
| examples_to_run | |
| end | |
| end | |
| end | |
| end | |
| unless ::Spec::VERSION::MAJOR > 1 || ::Spec::VERSION::MINOR > 0 | |
| module Spec | |
| module DSL | |
| class Example | |
| alias_method :initialize_old, :initialize | |
| def initialize(*args, &block) | |
| result = initialize_old(*args, &block) | |
| @from = caller(0)[3] | |
| Description.description.examples << self | |
| result | |
| end | |
| IN_METHOD_RE = /^(.+):in `(.+)'$/ | |
| def rspecTestName | |
| if @DLTK_backtrace.nil? | |
| backtrace = @from | |
| if backtrace =~ IN_METHOD_RE | |
| backtrace = $1 | |
| end | |
| @DLTK_backtrace = backtrace | |
| end | |
| description + '<' + @DLTK_backtrace | |
| end | |
| end | |
| end | |
| end | |
| module Spec | |
| module DSL | |
| class Description | |
| def self.description | |
| @@description | |
| end | |
| alias_method :initialize_old, :initialize | |
| def initialize(*args, &block) | |
| result = initialize_old(*args, &block) | |
| @@description = self | |
| result | |
| end | |
| def description_text | |
| description | |
| end | |
| def examples | |
| @examples ||= [] | |
| end | |
| end | |
| end | |
| end | |
| end | |
| module DLTK | |
| module RSpec | |
| module EnvVars | |
| # environment variable name to pass communication port number | |
| # to the launched script | |
| PORT = "RUBY_TESTING_PORT" | |
| PATH = "RUBY_TESTING_PATH" | |
| end # of EnvVars | |
| module MessageIds | |
| # Notification that a test run has started. | |
| # MessageIds.TEST_RUN_START + testCount.toString + " " + version | |
| TEST_RUN_START = "%TESTC " | |
| # Notification that a test run has ended. | |
| # TEST_RUN_END + elapsedTime.toString(). | |
| TEST_RUN_END = "%RUNTIME" | |
| # Notification about a test inside the test suite. | |
| # TEST_TREE + testId + "," + testName + "," + isSuite + "," + testcount | |
| # isSuite = "true" or "false" | |
| TEST_TREE = "%TSTTREE" | |
| #Notification that a test has started. | |
| # MessageIds.TEST_START + testID + "," + testName | |
| TEST_START = "%TESTS " | |
| # Notification that a test has ended. | |
| # TEST_END + testID + "," + testName | |
| TEST_END = "%TESTE " | |
| # Notification that a test had a error. | |
| # TEST_ERROR + testID + "," + testName. | |
| # After the notification follows the stack trace. | |
| TEST_ERROR = "%ERROR " | |
| # Notification that a test had a failure. | |
| # TEST_FAILED + testID + "," + testName. | |
| # After the notification follows the stack trace. | |
| TEST_FAILED = "%FAILED " | |
| # Notification that a test trace has started. | |
| # The end of the trace is signaled by a TRACE_END | |
| # message. In between the TRACE_START and TRACE_END | |
| # the stack trace is submitted as multiple lines. | |
| TRACE_START = "%TRACES " | |
| # Notification that a trace ends. | |
| TRACE_END = "%TRACEE " | |
| # Notification that the expected result has started. | |
| # The end of the expected result is signaled by a EXPECTED_END. | |
| EXPECTED_START = "%EXPECTS" | |
| # Notification that an expected result ends. | |
| EXPECTED_END = "%EXPECTE" | |
| # Notification that the actual result has started. | |
| # The end of the actual result is signaled by a ACTUAL_END. | |
| ACTUAL_START = "%ACTUALS" | |
| # Notification that an actual result ends. | |
| ACTUAL_END = "%ACTUALE" | |
| #Test identifier prefix for ignored tests. | |
| IGNORED_TEST_PREFIX = "@Ignore: " | |
| end # of MessageIds | |
| class SocketConnection | |
| def disconnect | |
| if @socket | |
| #debug "Closing socket" | |
| begin | |
| @socket.close | |
| rescue | |
| debug $!.to_s | |
| end | |
| @socket = nil | |
| #debug "Socket closed" | |
| end | |
| end | |
| def connectSocket(port) | |
| return false unless port > 0 | |
| #debug "Opening socket on #{port}" | |
| for i in 1..10 | |
| #debug "Iteration #{i}" | |
| begin | |
| @socket = TCPSocket.new('localhost', port) | |
| #debug "Socket opened" | |
| return true | |
| rescue | |
| #debug $!.to_s | |
| end | |
| sleep 1 | |
| end | |
| false | |
| end | |
| def sendMessage(message) | |
| #puts message | |
| if @socket | |
| @socket.puts message | |
| end | |
| end | |
| def notifyTestTreeEntry(testId, testName, hasChildren, testCount) | |
| sendMessage MessageIds::TEST_TREE + testId + ',' + escapeComma(testName) + ',' + hasChildren.to_s + ',' + testCount.to_s | |
| end | |
| def notifyTestStarted(testId, testName) | |
| sendMessage MessageIds::TEST_START + testId + "," + escapeComma(testName) | |
| end | |
| def notifyTestFailure(testId, testName, status) | |
| sendMessage status + testId + "," + escapeComma(testName) | |
| end | |
| def notifyTestEnded(testId, testName) | |
| sendMessage MessageIds::TEST_END + testId + "," + escapeComma(testName) | |
| end | |
| def notifyTestRunStarted(testCount) | |
| sendMessage MessageIds::TEST_RUN_START + testCount.to_s + " " + "v2" | |
| end | |
| def notifyTestRunEnded(elapsedTime) | |
| sendMessage MessageIds::TEST_RUN_END + elapsedTime.to_s | |
| end | |
| def escapeComma(s) | |
| s.gsub(/([\\,])/, '\\\\\1') | |
| end | |
| private :escapeComma | |
| end | |
| class DLTKFormatter < Spec::Runner::Formatter::BaseFormatter | |
| def initialize(*args) | |
| super | |
| @connection = SocketConnection.new() | |
| end | |
| def start(example_count) | |
| @connection.connectSocket ENV[EnvVars::PORT].to_i | |
| @connection.notifyTestRunStarted example_count | |
| end | |
| # tgrimm: Since rspec 1.2.4 add_example_group was renamed to example_groupe_started | |
| def add_example_group(example_group) | |
| example_group_started(example_group) | |
| if ::Spec::VERSION::MAJOR > 1 || ::Spec::VERSION::MINOR > 0 | |
| super | |
| end | |
| end | |
| def example_group_started(example_group) | |
| options = @options ? @options : ::Spec::Runner.options | |
| examples_to_run = example_group.examples | |
| examples_to_run = examples_to_run.reject do |example| | |
| matcher = ::Spec::Example::ExampleMatcher.new(example_group.description.to_s, example.description) | |
| !matcher.matches?(options.examples) | |
| end unless options.examples.empty? | |
| if examples_to_run.size > 0 then | |
| # ssanders: Ensure that description is never blank | |
| description = example_group.description || example_group.to_s | |
| @connection.notifyTestTreeEntry getTestId(example_group), description, true, examples_to_run.size | |
| examples_to_run.each do |e| | |
| @connection.notifyTestTreeEntry getTestId(e), getTestName(e), false, 1 | |
| end | |
| end | |
| end | |
| unless ::Spec::VERSION::MAJOR > 1 || ::Spec::VERSION::MINOR > 0 | |
| def add_behaviour(description) | |
| add_example_group(description) | |
| end | |
| end | |
| def example_started(example) | |
| @connection.notifyTestStarted getTestId(example), getTestName(example) | |
| end | |
| def example_passed(example) | |
| @connection.notifyTestEnded getTestId(example), getTestName(example) | |
| end | |
| def example_pending(behaviour, example, message = nil) | |
| # ssanders: In older versions and for "pending" the example is actually passed as behaviour | |
| example = behaviour if example.is_a?(String) | |
| @connection.notifyTestEnded getTestId(example), MessageIds::IGNORED_TEST_PREFIX + getTestName(example) | |
| end | |
| EXPECTED_GOT_RE = /^expected: (.+),\n\s+got: (.+) \(using (==|===)\)$/s | |
| def example_failed(example, counter, failure) | |
| testId = getTestId(example) | |
| testName = getTestName(example) | |
| f = failure.exception | |
| if failure.expectation_not_met? | |
| @connection.notifyTestFailure testId, testName, MessageIds::TEST_FAILED | |
| if f.message =~ EXPECTED_GOT_RE | |
| @connection.sendMessage MessageIds::EXPECTED_START | |
| @connection.sendMessage $1 | |
| @connection.sendMessage MessageIds::EXPECTED_END | |
| @connection.sendMessage MessageIds::ACTUAL_START | |
| @connection.sendMessage $2 | |
| @connection.sendMessage MessageIds::ACTUAL_END | |
| end | |
| else | |
| @connection.notifyTestFailure testId, testName, MessageIds::TEST_ERROR | |
| end | |
| @connection.sendMessage MessageIds::TRACE_START | |
| @connection.sendMessage f.message | |
| f.backtrace.each { |line| @connection.sendMessage line } | |
| @connection.sendMessage MessageIds::TRACE_END | |
| @connection.notifyTestEnded testId, testName | |
| end | |
| def dump_summary(duration, example_count, failure_count, pending_count) | |
| @connection.notifyTestRunEnded((duration * 1000).to_i) | |
| end | |
| def close | |
| @connection.disconnect | |
| @connection = nil | |
| end | |
| # internal | |
| def getTestId(example) | |
| return example.__id__.to_s | |
| end | |
| def getTestName(example) | |
| if example.respond_to?(:rspecTestName) | |
| name = example.rspecTestName | |
| elsif example.description | |
| name = example.description ? example.description : "NO NAME" | |
| if example.respond_to?(:location) | |
| name += "<" + example.location if example.location | |
| else | |
| name += "<" + example.backtrace if example.backtrace | |
| end | |
| end | |
| return name.to_s | |
| end | |
| end | |
| end | |
| end | |
| #if __FILE__ == $0 | |
| # ARGV.push 'bowling_spec.rb' | |
| #end | |
| ARGV.push '--format' | |
| ARGV.push 'DLTK::RSpec::DLTKFormatter' | |
| # tgrimm: RSpec 1.2.1 was release 7 days after 1.2.0, so there's no real reason to support 1.2.0 | |
| raise "RSpec 1.2.0 is not supported, please update RSpec" if ::Spec::VERSION::STRING == '1.2.0' | |
| if ::Spec::VERSION::MAJOR > 1 || ::Spec::VERSION::MINOR > 0 | |
| if ::Spec.constants.include?("Extensions") && ::Spec::Extensions::Main.private_method_defined?(:rspec_options) | |
| options = rspec_options | |
| else # ssanders: RSpec > 1.1.4 | |
| options = ::Spec::Runner.options | |
| end | |
| exit ::Spec::Runner::CommandLine.run(options) | |
| else # ssanders: RSpec < 1.1 | |
| exit ::Spec::Runner::CommandLine.run(ARGV, STDERR, STDOUT) | |
| end |