Skip to content

Commit

Permalink
[ftr] try capturing log output rather than writing everyting to console
Browse files Browse the repository at this point in the history
  • Loading branch information
spalger committed Aug 23, 2018
1 parent c7c3654 commit 6fbffb1
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 11 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@
"postcss": "^7.0.2",
"prettier": "^1.14.0",
"proxyquire": "1.7.11",
"regenerate": "^1.4.0",
"simple-git": "1.37.0",
"sinon": "^5.0.7",
"strip-ansi": "^3.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-dev-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
*/

export { withProcRunner } from './proc_runner';
export { ToolingLog, pickLevelFromFlags } from './tooling_log';
export { ToolingLog, ToolingLogTextWriter, pickLevelFromFlags } from './tooling_log';
4 changes: 3 additions & 1 deletion src/dev/mocha/__tests__/junit_report_generation.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ describe('dev/mocha/junit report generation', () => {
classname: sharedClassname,
name: 'SUITE works',
time: testPass.$.time,
}
},
'system-out': testPass['system-out']
});

expect(testFail.$.time).to.match(DURATION_REGEX);
Expand All @@ -104,6 +105,7 @@ describe('dev/mocha/junit report generation', () => {
name: 'SUITE fails',
time: testFail.$.time,
},
'system-out': testFail['system-out'],
failure: [
testFail.failure[0]
]
Expand Down
26 changes: 25 additions & 1 deletion src/dev/mocha/junit_report_generation.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ import { inspect } from 'util';

import mkdirp from 'mkdirp';
import xmlBuilder from 'xmlbuilder';
import stripAnsi from 'strip-ansi';
import regenerate from 'regenerate';

import { getSnapshotOfRunnableLogs } from './log_cache';

// create a regular expression using regenerate() that selects any character that is not explicitly allowed by https://www.w3.org/TR/xml/#NT-Char
const invalidXmlCharsRE = new RegExp(
`[^${
regenerate()
.add(0x9, 0xA, 0xD)
.addRange(0x20, 0xD7FF)
.addRange(0xE000, 0xFFFD)
.addRange(0x10000, 0x10FFFF)
.toString({ hasUnicodeFlag: true })
}]`,
'g'
);

function escapeCdata(string) {
return stripAnsi(string).replace(invalidXmlCharsRE, '');
}

export function setupJUnitReportGeneration(runner, options = {}) {
const {
Expand Down Expand Up @@ -120,9 +141,12 @@ export function setupJUnitReportGeneration(runner, options = {}) {

[...results, ...skippedResults].forEach(result => {
const el = addTestcaseEl(result.node);
el.ele('system-out').dat(
escapeCdata(getSnapshotOfRunnableLogs(result.node) || '')
);

if (result.failed) {
el.ele('failure').dat(inspect(result.error));
el.ele('failure').dat(escapeCdata(inspect(result.error)));
return;
}

Expand Down
62 changes: 62 additions & 0 deletions src/dev/mocha/log_cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

const cachedSuiteLogs = new WeakMap();
const cachesRunnableLogs = new WeakMap();

/**
* Add a chunk of log output to the cached
* output for a suite
* @param {Mocha.Suite} suite
* @param {string} chunk
*/
export function recordLog(suite, chunk) {
const cache = cachedSuiteLogs.get(suite) || '';
cachedSuiteLogs.set(suite, cache + chunk);
}

/**
* recursively walk up from a runnable to collect
* the cached log for its suite and all its parents
* @param {Mocha.Suite} suite
*/
function getCurrentCachedSuiteLogs(suite) {
const history = suite.parent ? getCurrentCachedSuiteLogs(suite.parent) : '';
const ownLogs = cachedSuiteLogs.get(suite) || '';
return history + ownLogs;
}

/**
* Snapshot the logs from this runnable's suite at this point,
* as the suite logs will get updated to include output from
* subsequent runnables
* @param {Mocha.Runnable} runnable
*/
export function snapshotLogsForRunnable(runnable) {
cachesRunnableLogs.set(runnable, getCurrentCachedSuiteLogs(runnable.parent));
}

/**
* Get the suite logs as they were when the logs for this runnable
* were snapshotted
* @param {Mocha.Runnable} runnable
*/
export function getSnapshotOfRunnableLogs(runnable) {
return cachesRunnableLogs.get(runnable);
}
4 changes: 4 additions & 0 deletions src/functional_test_runner/lib/config/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ export const schema = Joi.object().keys({
rootDirectory: Joi.string(),
}).default(),

mochaReporter: Joi.object().keys({
captureLogOutput: Joi.boolean().default(!!process.env.CI),
}).default(),

users: Joi.object().pattern(
ID_PATTERN,
Joi.object().keys({
Expand Down
51 changes: 43 additions & 8 deletions src/functional_test_runner/lib/mocha/reporter/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@
import { format } from 'util';

import Mocha from 'mocha';
import { ToolingLogTextWriter } from '@kbn/dev-utils';

import { setupJUnitReportGeneration } from '../../../../dev';
import * as colors from './colors';
import * as symbols from './symbols';
import { ms } from './ms';
import { writeEpilogue } from './write_epilogue';
import { recordLog, snapshotLogsForRunnable } from '../../../../dev/mocha/log_cache';

export function MochaReporterProvider({ getService }) {
const log = getService('log');
const config = getService('config');
let originalLogWriters;

return class MochaReporter extends Mocha.reporters.Base {
constructor(runner, options) {
Expand All @@ -55,11 +58,40 @@ export function MochaReporterProvider({ getService }) {
}

onStart = () => {
if (config.get('mochaReporter.captureLogOutput')) {
log.warning('debug logs are being captured, only error logs will be written to the console');
originalLogWriters = log.getWriters();
log.setWriters([
new ToolingLogTextWriter({
level: 'error',
writeTo: process.stdout
}),
new ToolingLogTextWriter({
level: 'debug',
writeTo: {
write: (chunk) => {
// if the current runnable is a beforeEach hook then
// `runner.suite` is set to the suite that defined the
// hook, rather than the suite executing, so instead we
// grab the suite from the test, but that's only available
// when we are doing something test specific, so for global
// hooks we fallback to `runner.suite`
const currentSuite = this.runner.test
? this.runner.test.parent
: this.runner.suite;

recordLog(currentSuite, chunk);
}
}
})
]);
}

log.write('');
}

onHookStart = hook => {
log.write('-> ' + colors.suite(hook.title));
log.write(`-> ${colors.suite(hook.title)}`);
log.indent(2);
}

Expand All @@ -76,7 +108,7 @@ export function MochaReporterProvider({ getService }) {
}

onSuiteEnd = () => {
if (log.indent(-2) === '') {
if (log.indent(-2) === 0) {
log.write();
}
}
Expand All @@ -86,7 +118,8 @@ export function MochaReporterProvider({ getService }) {
log.indent(2);
}

onTestEnd = () => {
onTestEnd = (test) => {
snapshotLogsForRunnable(test);
log.indent(-2);
}

Expand All @@ -96,7 +129,6 @@ export function MochaReporterProvider({ getService }) {
}

onPass = test => {

let time = '';
if (test.speed !== 'fast') {
time = colors.speed(test.speed, ` (${ms(test.duration)})`);
Expand All @@ -106,7 +138,7 @@ export function MochaReporterProvider({ getService }) {
log.write(`- ${pass} ${time}`);
}

onFail = test => {
onFail = runnable => {
// NOTE: this is super gross
//
// - I started by trying to extract the Base.list() logic from mocha
Expand All @@ -118,14 +150,13 @@ export function MochaReporterProvider({ getService }) {
const realLog = console.log;
console.log = (...args) => output += `${format(...args)}\n`;
try {
Mocha.reporters.Base.list([test]);
Mocha.reporters.Base.list([runnable]);
} finally {
console.log = realLog;
}

log.write(
`- ${symbols.err} ` +
colors.fail(`fail: "${test.fullTitle()}"`) +
`- ${colors.fail(`${symbols.err} fail: "${runnable.fullTitle()}"`)}` +
'\n' +
output
.split('\n')
Expand All @@ -139,6 +170,10 @@ export function MochaReporterProvider({ getService }) {
}

onEnd = () => {
if (originalLogWriters) {
log.setWriters(originalLogWriters);
}

writeEpilogue(log, this.stats);
}
};
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11346,6 +11346,10 @@ regenerate@^1.2.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"

regenerate@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"

regenerator-runtime@^0.10.0:
version "0.10.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
Expand Down

0 comments on commit 6fbffb1

Please sign in to comment.