diff --git a/libraries/tools/kotlin-test-js-runner/build.gradle.kts b/libraries/tools/kotlin-test-js-runner/build.gradle.kts index 61e9e485d48..c63fc1667d8 100644 --- a/libraries/tools/kotlin-test-js-runner/build.gradle.kts +++ b/libraries/tools/kotlin-test-js-runner/build.gradle.kts @@ -51,6 +51,7 @@ tasks { "nodejs.ts", "karma.ts", "karma-kotlin-reporter.js", + "mocha-kotlin-reporter.js", "nodejs-source-map-support.js", "package.json", "rollup.config.js", diff --git a/libraries/tools/kotlin-test-js-runner/mocha-kotlin-reporter.js b/libraries/tools/kotlin-test-js-runner/mocha-kotlin-reporter.js new file mode 100644 index 00000000000..fd670d5c4d5 --- /dev/null +++ b/libraries/tools/kotlin-test-js-runner/mocha-kotlin-reporter.js @@ -0,0 +1,172 @@ +/** + * From mocha-teamcity-reporter + * The MIT License + * Copyright (c) 2016 Jamie Sherriff + */ + +/** + * Teamcity doc reference https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity + * + * Module dependencies. + */ +'use strict'; + +const processPID = process.pid.toString(); +const TEST_IGNORED = `##teamcity[testIgnored name='%s' message='%s' flowId='%s']`; +const SUITE_START = `##teamcity[testSuiteStarted name='%s' flowId='%s']`; +const SUITE_END = `##teamcity[testSuiteFinished name='%s' duration='%s' flowId='%s']`; +const SUITE_END_NO_DURATION = `##teamcity[testSuiteFinished name='%s' flowId='%s']`; +const TEST_START = `##teamcity[testStarted name='%s' captureStandardOutput='true' flowId='%s']`; +const TEST_FAILED = `##teamcity[testFailed name='%s' message='%s' details='%s' captureStandardOutput='true' flowId='%s']`; +const TEST_FAILED_COMPARISON = `##teamcity[testFailed type='comparisonFailure' name='%s' message='%s' \ +details='%s' captureStandardOutput='true' actual='%s' expected='%s' flowId='%s']`; +const TEST_END = `##teamcity[testFinished name='%s' duration='%s' flowId='%s']`; +const TEST_END_NO_DURATION = `##teamcity[testFinished name='%s' flowId='%s']`; + +const util = require('util'); + +let Base, log, logError; + +Base = require('mocha').reporters.Base; +log = console.log; +logError = console.error; + +/** + * Escape the given `str`. + */ + +function escape(str) { + if (!str) return ''; + return str + .toString() + .replace(/\x1B.*?m/g, '') // eslint-disable-line no-control-regex + .replace(/\|/g, '||') + .replace(/\n/g, '|n') + .replace(/\r/g, '|r') + .replace(/\[/g, '|[') + .replace(/\]/g, '|]') + .replace(/\u0085/g, '|x') + .replace(/\u2028/g, '|l') + .replace(/\u2029/g, '|p') + .replace(/'/g, '|\''); +} + +function isNil(value) { + return value == null; // eslint-disable-line +} + +function formatString() { + let formattedArguments = []; + const args = Array.prototype.slice.call(arguments, 0); + // Format all arguments for TC display (it escapes using the pipe char). + let tcMessage = args.shift(); + args.forEach((param) => { + formattedArguments.push(escape(param)); + }); + formattedArguments.unshift(tcMessage); + return util.format.apply(util, formattedArguments); +} + + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @param {options} options + * @api public + */ + +function Teamcity(runner, options) { + options = options || {}; + const reporterOptions = options.reporterOptions || {}; + let flowId, useStdError, recordHookFailures, actualVsExpected; + (reporterOptions.flowId) ? flowId = reporterOptions.flowId : flowId = process.env['MOCHA_TEAMCITY_FLOWID'] || processPID; + (reporterOptions.useStdError) ? useStdError = reporterOptions.useStdError : useStdError = process.env['USE_STD_ERROR']; + (reporterOptions.recordHookFailures) ? recordHookFailures = reporterOptions.recordHookFailures : recordHookFailures = + process.env['RECORD_HOOK_FAILURES']; + (reporterOptions.actualVsExpected) ? actualVsExpected = reporterOptions.actualVsExpected : actualVsExpected = + process.env['ACTUAL_VS_EXPECTED']; + (useStdError) ? useStdError = (useStdError.toLowerCase() === 'true') : useStdError = false; + (recordHookFailures) ? recordHookFailures = (recordHookFailures.toLowerCase() === 'true') : recordHookFailures = false; + actualVsExpected ? actualVsExpected = (actualVsExpected.toLowerCase() === 'true') : actualVsExpected = false; + Base.call(this, runner); + let stats = this.stats; + const topLevelSuite = reporterOptions.topLevelSuite || process.env['MOCHA_TEAMCITY_TOP_LEVEL_SUITE']; + + runner.on('suite', function (suite) { + if (suite.root) { + if (topLevelSuite) { + log(formatString(SUITE_START, topLevelSuite, flowId)); + } + return; + } + suite.startDate = new Date(); + log(formatString(SUITE_START, suite.title, flowId)); + }); + + runner.on('test', function (test) { + log(formatString(TEST_START, test.title, flowId)); + }); + + runner.on('fail', function (test, err) { + if (actualVsExpected && (err.actual && err.expected)) { + if (useStdError) { + logError(formatString(TEST_FAILED_COMPARISON, test.title, err.message, err.stack, err.actual, err.expected, flowId)); + } + else { + log(formatString(TEST_FAILED_COMPARISON, test.title, err.message, err.stack, err.actual, err.expected, flowId)); + } + } + else { + if (useStdError) { + logError(formatString(TEST_FAILED, test.title, err.message, err.stack, flowId)); + } + else { + log(formatString(TEST_FAILED, test.title, err.message, err.stack, flowId)); + } + } + }); + + runner.on('pending', function (test) { + log(formatString(TEST_IGNORED, test.title, test.title, flowId)); + }); + + runner.on('test end', function (test) { + // This is necessary not to emit `test end` event on skipped tests + if (test.isPending()) return + + if (isNil(test.duration)) { + log(formatString(TEST_END_NO_DURATION, test.title, flowId)); + } + else { + log(formatString(TEST_END, test.title, test.duration.toString(), flowId)); + } + }); + + runner.on('hook', function (test) { + if (recordHookFailures) { + log(formatString(TEST_START, test.title, flowId)); + } + }); + + runner.on('suite end', function (suite) { + if (suite.root) return; + log(formatString(SUITE_END, suite.title, new Date() - suite.startDate, flowId)); + }); + + runner.on('end', function () { + let duration; + (typeof stats === 'undefined') ? duration = null : duration = stats.duration; + if (topLevelSuite) { + isNil(duration) ? log(formatString(SUITE_END_NO_DURATION, topLevelSuite, flowId)) : log( + formatString(SUITE_END, topLevelSuite, duration, flowId)); + } + }); +} + + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; \ No newline at end of file diff --git a/libraries/tools/kotlin-test-js-runner/package.json b/libraries/tools/kotlin-test-js-runner/package.json index 34f0f817861..8f221f49606 100644 --- a/libraries/tools/kotlin-test-js-runner/package.json +++ b/libraries/tools/kotlin-test-js-runner/package.json @@ -11,7 +11,7 @@ "lib/**/*" ], "scripts": { - "build": "rimraf lib/* && rollup -c rollup.config.js && copyfiles \"karma-kotlin-reporter.js\" lib" + "build": "rimraf lib/* && rollup -c rollup.config.js && copyfiles \"karma-kotlin-reporter.js\" \"mocha-kotlin-reporter.js\" lib" }, "dependencies": { "@types/node": "^10.12.21", diff --git a/license/README.md b/license/README.md index cef8660c727..f49262ed169 100644 --- a/license/README.md +++ b/license/README.md @@ -187,6 +187,10 @@ any distributions of the tools or libraries: and [license/third_party/karma-teamcity-reporter_LICENSE.txt](third_party/karma-teamcity-reporter_LICENSE.txt)) - Origin: Copyright (C) 2011-2019 Google, Inc. and Copyright (C) 2011-2013 Vojta Jína and contributors. + - Path: libraries/tools/kotlin-test-js-runner/mocha-kotlin-reporter.js + - License: MIT ([license/third_party/mocha-teamcity-reporter_LICENSE.txt](third_party/mocha-teamcity-reporter_LICENSE.txt)) + - Origin: Copyright (c) 2016 Jamie Sherriff + - Path: libraries/tools/kotlin-test-js-runner/src/utils.ts - License: MIT ([license/third_party/teamcity-service-messages_LICENSE.txt](third_party/teamcity-service-messages_LICENSE.txt) and [license/third_party/lodash_LICENSE.txt](third_party/lodash_LICENSE.txt)) diff --git a/license/third_party/mocha-teamcity-reporter_LICENSE.txt b/license/third_party/mocha-teamcity-reporter_LICENSE.txt new file mode 100644 index 00000000000..23ca9cd4d7f --- /dev/null +++ b/license/third_party/mocha-teamcity-reporter_LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Jamie Sherriff + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file