| # |
| # Copyright (c) 2008, 2016 xored software, Inc. and others |
| # All rights reserved. This program and the accompanying materials |
| # are made available under the terms of the Eclipse Public License v1.0 |
| # which accompanies this distribution, and is available at |
| # http://www.eclipse.org/legal/epl-v10.html |
| # |
| # Contributors: |
| # xored software, Inc. - initial API and Implementation (Alex Panchenko) |
| # |
| require 'test/unit' |
| require 'test/unit/ui/console/testrunner' |
| require 'socket' |
| |
| module DLTK |
| module TestUnit |
| module EnvVars |
| # environment variable name to pass communication port number |
| # to the launched script |
| PORT = "RUBY_TESTING_PORT" |
| PATH = "RUBY_TESTING_PATH" |
| end |
| |
| 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 Runner < Test::Unit::UI::Console::TestRunner |
| def start |
| connectSocket ENV[EnvVars::PORT].to_i |
| @testsByName = {} |
| super |
| ensure |
| disconnect(nil) |
| end |
| |
| DLTK = "DLTK/#{__id__}" |
| |
| def attach_to_mediator |
| super |
| @mediator.add_listener(Test::Unit::TestResult::FAULT, &method(:onTestFailure)) |
| @mediator.add_listener(Test::Unit::UI::TestRunnerMediator::STARTED, DLTK, &method(:connect)) |
| @mediator.add_listener(Test::Unit::UI::TestRunnerMediator::FINISHED, DLTK, &method(:disconnect)) |
| @mediator.add_listener(Test::Unit::TestCase::STARTED, DLTK, &method(:onTestStarted)) |
| @mediator.add_listener(Test::Unit::TestCase::FINISHED, DLTK, &method(:onTestFinished)) |
| end |
| |
| def debug(msg) |
| puts "[DEBUG] #{msg}" |
| end |
| |
| def connect(result) |
| @startTime = Time.now |
| notifyTestRunStarted @suite.size |
| sendTree(@suite) |
| 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 disconnect(result) |
| if @socket |
| notifyTestRunEnded ((Time.now.to_f - @startTime.to_f) * 1000).to_i |
| #debug "Closing socket" |
| begin |
| @socket.close |
| rescue |
| debug $!.to_s |
| end |
| @socket = nil |
| #debug "Socket closed" |
| end |
| end |
| |
| def onTestStarted(name) |
| t = @testsByName[name] |
| if t |
| notifyTestStarted getTestId(t), t.name |
| else |
| #debug "warn: test #{name} not found" |
| notifyTestStarted name, name |
| end |
| end |
| |
| def onTestFinished(name) |
| t = @testsByName[name] |
| if t |
| notifyTestEnded getTestId(t), t.name |
| else |
| #debug "warn: test #{name} not found" |
| notifyTestEnded name, name |
| end |
| end |
| |
| ACTUAL_EXPECTED_RE = /^<(.+)> expected but was\n<(.+)>.$/s |
| |
| def onTestFailure(f) |
| if f.is_a? Test::Unit::Failure |
| t = @testsByName[f.test_name] |
| if t |
| notifyTestFailure getTestId(t), t.name, MessageIds::TEST_FAILED |
| else |
| notifyTestFailure f.test_name, f.test_name, MessageIds::TEST_FAILED |
| end |
| if f.message =~ ACTUAL_EXPECTED_RE |
| sendMessage MessageIds::EXPECTED_START |
| sendMessage $1 |
| sendMessage MessageIds::EXPECTED_END |
| sendMessage MessageIds::ACTUAL_START |
| sendMessage $2 |
| sendMessage MessageIds::ACTUAL_END |
| end |
| sendMessage MessageIds::TRACE_START |
| sendMessage f.message |
| f.location.each { |line| sendMessage line } |
| sendMessage MessageIds::TRACE_END |
| elsif f.is_a? Test::Unit::Error |
| t = @testsByName[f.test_name] |
| if t |
| notifyTestFailure getTestId(t), t.name, MessageIds::TEST_ERROR |
| else |
| notifyTestFailure f.test_name, f.test_name, MessageIds::TEST_ERROR |
| end |
| sendMessage MessageIds::TRACE_START |
| e = f.exception |
| sendMessage e.message |
| e.backtrace.each { |line| sendMessage line } |
| sendMessage MessageIds::TRACE_END |
| end |
| 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 sendTree(suite) |
| suite.tests.each do |t| |
| @testsByName[t.name] = t |
| if t.is_a? Test::Unit::TestSuite |
| notifyTestTreeEntry getTestId(t), t.name, true, t.size |
| sendTree t |
| else |
| notifyTestTreeEntry getTestId(t), t.name, false, 1 |
| end |
| 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 notifyTestEnded(testId, testName) |
| sendMessage MessageIds::TEST_END + testId + "," + escapeComma(testName) |
| end |
| |
| def notifyTestFailure(testId, testName, status) |
| sendMessage status + testId + "," + escapeComma(testName) |
| end |
| |
| def getTestId(t) |
| return t.__id__.to_s |
| end |
| |
| def escapeComma(s) |
| s.gsub(/([\\,])/, '\\\\\1') |
| end |
| |
| def sendMessage(message) |
| #debug message |
| if @socket |
| @socket.puts message |
| end |
| end |
| |
| end |
| end |
| end |
| |
| at_exit do |
| unless $! || Test::Unit.run? |
| port = ENV[DLTK::TestUnit::EnvVars::PORT].to_i |
| if port != 0 |
| path = ENV[DLTK::TestUnit::EnvVars::PATH] |
| autoRunner = Test::Unit::AutoRunner.new(path != nil) |
| if RUBY_VERSION >= "1.9" |
| autoRunner.runner_options[:output_level] = Test::Unit::UI::Console::OutputLevel::SILENT |
| else |
| autoRunner.output_level = Test::Unit::UI::SILENT |
| end |
| autoRunner.base = path if path |
| # ssanders - Support non-standard test filenames (e.g. Rails) |
| autoRunner.pattern = [/\b.*_test\.rb\Z/m] |
| # ssanders - Avoid running the 'default_test' dummy method (e.g. Rails), |
| # required to satisfy Test::Unit always needing at least one test method |
| autoRunner.filters = [proc { |t| /default_test/ =~ t.method_name ? false : nil }] |
| autoRunner.runner = proc do |r| |
| DLTK::TestUnit::Runner |
| end |
| exit autoRunner.run |
| end |
| end |
| end |
| |
| if __FILE__ == $0 |
| #debug mode |
| #require 'test_math' |
| #require 'test_shoulda' |
| end |