Skip to content

Commit

Permalink
feat: show command history on error
Browse files Browse the repository at this point in the history
  • Loading branch information
Rashid Ksirov committed Mar 28, 2020
1 parent 5c1f7e6 commit 32b5762
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 12 deletions.
51 changes: 51 additions & 0 deletions lib/history-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

const {takeRightWhile, isUndefined} = require('lodash');

const normalizeArg = (arg) => typeof arg === 'object' ? JSON.stringify(arg) : `"${arg}"`;

const getCommandWithArgs = ({name, args = []}) => `${name}(${args.map(normalizeArg).join(', ')})`;

const getFileFromStack = ({stack}) => {
if (!stack || typeof stack !== 'string') {
return '';
}

const openingParenthesisPos = stack.lastIndexOf('(');
if (openingParenthesisPos !== -1) {
const closingParenthesisPos = stack.lastIndexOf(')');

return stack.substring(openingParenthesisPos + 1, closingParenthesisPos);
}

return stack;
};

const getCommandWithArgsAndFile = (record) => `${getCommandWithArgs(record)}: ${getFileFromStack(record)}`;

const getCommandHistory = (allHistory, runnableFile) => {
if (isUndefined(allHistory)) {
return;
}

try {
const commands = allHistory
.filter(({stack}) => stack.includes(runnableFile))
.map(getCommandWithArgs)
.map((s) => `\t${s}\n`);

const lastSubHistory = takeRightWhile(allHistory, ({stack}) => !stack.includes(runnableFile));

const lastSubCommands = lastSubHistory
.map(getCommandWithArgsAndFile)
.map((s) => `\t\t${s}\n`);

return [...commands, ...lastSubCommands];
} catch (e) {
return `failed to get command history: ${e.message}`;
}
};

module.exports = {
getCommandHistory
};
9 changes: 8 additions & 1 deletion lib/test-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const crypto = require('crypto');

const SuiteAdapter = require('./suite-adapter');
const {getSuitePath} = require('./plugin-utils').getHermioneUtils();
const {getCommandHistory} = require('./history-utils');
const {SUCCESS, FAIL, ERROR, UPDATED} = require('./constants/test-statuses');
const {ERROR_DETAILS_PATH} = require('./constants/paths');
const utils = require('./server-utils');
Expand Down Expand Up @@ -210,7 +211,13 @@ module.exports = class TestAdapter {
}

get error() {
return _.pick(this._testResult.err, ['message', 'stack', 'stateName']);
const err = _.pick(this._testResult.err, ['message', 'stack', 'history', 'stateName']);

if (err.history) {
err.history = getCommandHistory(err.history, this._testResult.file);
}

return err;
}

get imageDir() {
Expand Down
36 changes: 36 additions & 0 deletions test/unit/lib/history-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

const {getCommandHistory} = require('lib/history-utils');

describe('history-utils', () => {
describe('getCommandHistory', () => {
it('should return commands executed in test file and all sub commands of the last command', async () => {
const allHistory = [
{name: 'foo', args: ['foo-arg'], stack: 'foo("foo-arg") (/path/to/test/file:10:4)'},
{name: 'baz', args: ['baz-arg'], stack: 'baz("baz-arg") (/path/to/baz/file:20:4)'},
{name: 'bar', args: ['bar-arg'], stack: 'bar("bar-arg") (/path/to/test/file:11:4)'},
{name: 'qux', args: ['qux-arg'], stack: 'qux("qux-arg") (/path/to/qux/file:21:4)'}
];

const history = await getCommandHistory(allHistory, '/path/to/test/file');

assert.deepEqual(history, [
'\tfoo("foo-arg")\n',
'\tbar("bar-arg")\n',
'\t\tqux("qux-arg"): /path/to/qux/file:21:4\n'
]);
});

it('should return undefined if all history is not given', async () => {
const history = await getCommandHistory(undefined, '/path/to/test/file');

assert.isUndefined(history);
});

it('should return failure message in case of exception', async () => {
const history = await getCommandHistory([{}], '/path/to/test/file');

assert.match(history, /failed to get command history: .*/);
});
});
});
9 changes: 5 additions & 4 deletions test/unit/lib/static/components/section/state-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ describe('<StateError/> component', () => {
};

describe('"errorPattern" prop is not passed', () => {
it('should render error "message" and "stack" if "errorPattern" prop is not passed', () => {
const error = {message: 'some-msg', stack: 'some-stack'};
it('should render error "message", "stack" and "history" if "errorPattern" prop is not passed', () => {
const error = {message: 'some-msg', stack: 'some-stack', history: 'some-history'};

const component = mkStateErrorComponent({error});

assert.equal(component.find('.error__item').first().text(), 'message: some-msg');
assert.equal(component.find('.error__item').last().text(), 'stack: some-stack');
assert.equal(component.find('.error__item').at(0).text(), 'message: some-msg');
assert.equal(component.find('.error__item').at(1).text(), 'stack: some-stack');
assert.equal(component.find('.error__item').at(2).text(), 'history: some-history');
});

it('should break error fields by line break', () => {
Expand Down
6 changes: 3 additions & 3 deletions test/unit/lib/static/modules/reducers/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ describe('lib/static/modules/reducers', () => {
'url',
JSON.stringify({muted: false}),
'description',
JSON.stringify({message: 'error message', stack: 'error stack'}),
JSON.stringify({message: 'error message', stack: 'error stack', history: 'some history'}),
'skipReason',
JSON.stringify([{actualImg: {path: 'path', size: {width: 0, height: 0}}}]),
Number(true), // multiple tabs
Expand All @@ -326,7 +326,7 @@ describe('lib/static/modules/reducers', () => {
'url',
JSON.stringify({muted: false}),
'description',
JSON.stringify({message: 'error message', stack: 'error stack'}),
JSON.stringify({message: 'error message', stack: 'error stack', history: 'some history'}),
'skipReason',
JSON.stringify([{actualImg: {path: 'path', size: {width: 0, height: 0}}}]),
Number(true), // multiple tabs
Expand All @@ -341,7 +341,7 @@ describe('lib/static/modules/reducers', () => {
'url',
JSON.stringify({muted: false}),
'description',
JSON.stringify({message: 'error message', stack: 'error stack'}),
JSON.stringify({message: 'error message', stack: 'error stack', history: 'some history'}),
'skipReason',
JSON.stringify([{actualImg: {path: 'path', size: {width: 0, height: 0}}}]),
Number(true), // multiple tabs
Expand Down
17 changes: 13 additions & 4 deletions test/unit/lib/test-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const fs = require('fs-extra');

describe('hermione test adapter', () => {
const sandbox = sinon.sandbox.create();
let tmp, HermioneTestResultAdapter, err, getSuitePath;
let tmp, HermioneTestResultAdapter, err, getSuitePath, getCommandHistory;

class ImageDiffError extends Error {}
class NoRefImageError extends Error {}
Expand Down Expand Up @@ -50,10 +50,12 @@ describe('hermione test adapter', () => {
beforeEach(() => {
tmp = {tmpdir: 'default/dir'};
getSuitePath = sandbox.stub();
getCommandHistory = sandbox.stub();

HermioneTestResultAdapter = proxyquire('../../../lib/test-adapter', {
tmp,
'../plugin-utils': {getHermioneUtils: () => ({getSuitePath})}
'./plugin-utils': {getHermioneUtils: () => ({getSuitePath})},
'./history-utils': {getCommandHistory}
});
sandbox.stub(utils, 'getCurrentPath').returns('');
sandbox.stub(utils, 'getDiffPath').returns('');
Expand Down Expand Up @@ -83,10 +85,16 @@ describe('hermione test adapter', () => {
assert.equal(result.attempt, 0);
});

it('should return test error with "message", "stack" and "stateName"', () => {
it('should return test error with "message", "stack", "history" and "stateName"', () => {
getCommandHistory.withArgs([{name: 'foo'}], 'bar').returns(['some-history']);
const testResult = mkTestResult_({
file: 'bar',
err: {
message: 'some-message', stack: 'some-stack', stateName: 'some-test', foo: 'bar'
message: 'some-message',
stack: 'some-stack',
history: [{name: 'foo'}],
stateName: 'some-test',
foo: 'bar'
}
});

Expand All @@ -95,6 +103,7 @@ describe('hermione test adapter', () => {
assert.deepEqual(hermioneTestAdapter.error, {
message: 'some-message',
stack: 'some-stack',
history: ['some-history'],
stateName: 'some-test'
});
});
Expand Down

0 comments on commit 32b5762

Please sign in to comment.