gpu.js/test/internal/function-tracer.js
2020-05-02 11:50:53 -04:00

540 lines
16 KiB
JavaScript

const { assert, test, skip, module: describe, only } = require('qunit');
const sinon = require('sinon');
const acorn = require('acorn');
const { FunctionTracer } = require('../../src');
describe('internal: FunctionTracer');
test('works with Program', () => {
const ast = acorn.parse(`var i;`);
const functionTracer = new FunctionTracer(ast);
assert.ok(functionTracer.functionContexts.length > 0);
});
test('works with BlockStatement', () => {
const mockBody = {};
let called = false;
let calledBody = null;
const mockInstance = {
contexts: [],
runningContexts: [],
newContext: FunctionTracer.prototype.newContext,
scan: (body) => {
called = true;
calledBody = body;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'BlockStatement', body: mockBody });
assert.ok(called);
assert.equal(calledBody, mockBody);
assert.equal(mockInstance.contexts.length, 1);
});
test('works with AssignmentExpression', () => {
const mockLeft = {};
const mockRight = {};
let called = false;
let calledSides = [];
const mockInstance = {
scan: (side) => {
called = true;
calledSides.push(side);
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'AssignmentExpression', left: mockLeft, right: mockRight });
assert.ok(called);
assert.deepEqual(calledSides, [mockLeft, mockRight]);
});
test('works with LogicalExpression', () => {
const mockLeft = {};
const mockRight = {};
let called = false;
let calledSides = [];
const mockInstance = {
scan: (side) => {
called = true;
calledSides.push(side);
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'LogicalExpression', left: mockLeft, right: mockRight });
assert.ok(called);
assert.deepEqual(calledSides, [mockLeft, mockRight]);
});
test('works with BinaryExpression', () => {
const mockLeft = {};
const mockRight = {};
let called = false;
let calledSides = [];
const mockInstance = {
scan: (side) => {
called = true;
calledSides.push(side);
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'BinaryExpression', left: mockLeft, right: mockRight });
assert.ok(called);
assert.deepEqual(calledSides, [mockLeft, mockRight]);
});
test('works with UpdateExpression', () => {
const mockArgument = {};
let called = false;
let calledBody = null;
const mockInstance = {
scan: (argument) => {
called = true;
calledBody = argument;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'UpdateExpression', argument: mockArgument });
assert.ok(called);
assert.equal(calledBody, mockArgument);
});
test('works with UnaryExpression', () => {
const mockArgument = {};
let called = false;
let calledArgument = null;
const mockInstance = {
scan: (argument) => {
called = true;
calledArgument = argument;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'UpdateExpression', argument: mockArgument });
assert.ok(called);
assert.equal(calledArgument, mockArgument);
});
test('works with VariableDeclaration', () => {
const mockDeclarations = [];
let called = false;
let calledDeclarations = null;
const mockInstance = {
scan: (declarations) => {
called = true;
calledDeclarations = declarations;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'VariableDeclaration', declarations: mockDeclarations });
assert.ok(called);
assert.deepEqual(calledDeclarations, mockDeclarations);
});
test('works with generic VariableDeclarator', () => {
const ast = acorn.parse('var bob = 0;');
const functionTracer = new FunctionTracer(ast);
const { bob } = functionTracer.contexts[0];
assert.equal(bob.ast, ast.body[0].declarations[0]);
assert.equal(bob.context, functionTracer.contexts[0]);
assert.equal(bob.name, 'bob');
assert.equal(bob.origin, 'declaration');
assert.equal(bob.assignable, true);
assert.equal(bob.inForLoopTest, null);
assert.equal(bob.inForLoopInit, false);
assert.equal(functionTracer.declarations[0], bob);
});
test('works with var VariableDeclarator', () => {
const ast = acorn.parse('var bob = 0;');
const functionTracer = new FunctionTracer(ast);
const { bob } = functionTracer.contexts[0];
assert.equal(bob.context['@contextType'], 'function');
});
test('works with let VariableDeclarator', () => {
const ast = acorn.parse('let bob = 0;');
const functionTracer = new FunctionTracer(ast);
const { bob } = functionTracer.contexts[0];
assert.equal(bob.context['@contextType'], 'function');
});
test('works with var & let VariableDeclarator together', () => {
const ast = acorn.parse(`var bob = 0;
for (let i = 0; i < 1; i++) { let pop = 0; }`);
const functionTracer = new FunctionTracer(ast);
assert.equal(functionTracer.contexts[0].bob.context['@contextType'], 'function');
assert.equal(functionTracer.contexts[0].i, undefined);
assert.equal(functionTracer.contexts[0].pop, undefined);
assert.equal(functionTracer.contexts[1].bob.context['@contextType'], 'function');
assert.equal(functionTracer.contexts[1].i.context['@contextType'], 'function');
assert.equal(functionTracer.contexts[1].pop, undefined);
assert.equal(functionTracer.contexts[2].bob.context['@contextType'], 'function');
assert.equal(functionTracer.contexts[2].i.context['@contextType'], 'function');
assert.equal(functionTracer.contexts[2].pop, undefined);
assert.equal(functionTracer.contexts[3].bob.context['@contextType'], 'function');
assert.equal(functionTracer.contexts[3].i.context['@contextType'], 'function');
assert.equal(functionTracer.contexts[3].pop.context['@contextType'], 'function');
});
test('works with FunctionExpression when runningContexts.length = 0', () => {
const mockBody = {};
let called = false;
let calledBody = null;
const mockInstance = {
runningContexts: [],
functions: [],
scan: (body) => {
called = true;
calledBody = body;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'FunctionExpression', body: mockBody });
assert.ok(called);
assert.equal(calledBody, mockBody);
assert.equal(mockInstance.functions.length, 0);
});
test('works with FunctionDeclaration when runningContexts.length = 0', () => {
const mockBody = {};
let called = false;
let calledBody = null;
const mockInstance = {
runningContexts: [],
functions: [],
scan: (body) => {
called = true;
calledBody = body;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'FunctionDeclaration', body: mockBody });
assert.ok(called);
assert.equal(calledBody, mockBody);
assert.equal(mockInstance.functions.length, 0);
});
test('works with FunctionExpression when runningContexts.length > 0', () => {
const mockBody = {};
const mockInstance = {
functions: [],
runningContexts: [null],
scan: () => {
throw new Error('should not be called');
}
};
const mockAst = { type: 'FunctionExpression', body: mockBody };
FunctionTracer.prototype.scan.call(mockInstance, mockAst);
assert.equal(mockInstance.functions.length, 1);
assert.equal(mockInstance.functions[0], mockAst);
});
test('works with FunctionDeclaration when runningContexts.length > 0', () => {
const mockBody = {};
const mockInstance = {
functions: [],
runningContexts: [null],
scan: () => {
throw new Error('should not be called');
}
};
const mockAst = { type: 'FunctionDeclaration', body: mockBody };
FunctionTracer.prototype.scan.call(mockInstance, mockAst);
assert.equal(mockInstance.functions.length, 1);
assert.equal(mockInstance.functions[0], mockAst);
});
test('works with IfStatement', () => {
const mockTest = {};
const mockConsequent = {};
const mockAlternate = {};
let called = false;
let calledArgs = [];
const mockInstance = {
scan: (arg) => {
called = true;
calledArgs.push(arg);
}
};
FunctionTracer.prototype.scan.call(mockInstance, {
type: 'IfStatement',
test: mockTest,
consequent: mockConsequent,
alternate: mockAlternate,
});
assert.ok(called);
assert.deepEqual(calledArgs, [mockTest, mockConsequent, mockAlternate]);
});
test('works with ForStatement', () => {
const ast = acorn.parse(`for (let i = 0; i < 1; i++) {
call();
}`);
const functionTracer = new FunctionTracer(ast.body[0]);
assert.equal(functionTracer.declarations[0].name, 'i');
assert.equal(functionTracer.contexts.length, 4);
});
test('works with DoWhileStatement', () => {
const mockBody = {};
const mockTest = {};
let called = false;
let calledArgs = [];
const mockInstance = {
contexts: [],
runningContexts: [],
newContext: FunctionTracer.prototype.newContext,
scan: (arg) => {
called = true;
calledArgs.push(arg);
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'DoWhileStatement', body: mockBody, test: mockTest });
assert.ok(called);
assert.deepEqual(calledArgs, [mockBody, mockTest]);
});
test('works with WhileStatement', () => {
const mockBody = {};
const mockTest = {};
let called = false;
let calledArgs = [];
const mockInstance = {
contexts: [],
runningContexts: [],
newContext: FunctionTracer.prototype.newContext,
scan: (arg) => {
called = true;
calledArgs.push(arg);
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'WhileStatement', body: mockBody, test: mockTest });
assert.ok(called);
assert.deepEqual(calledArgs, [mockBody, mockTest]);
});
test('works with Identifier', () => {
const mockCurrentContext = {};
const mockIsState = sinon.spy();
const mockGetDeclaration = sinon.spy(() => 123);
const mockInstance = {
identifiers: [],
currentContext: mockCurrentContext,
isState: mockIsState,
getDeclaration: mockGetDeclaration,
};
const mockAst = { type: 'Identifier', name: 'x' };
FunctionTracer.prototype.scan.call(mockInstance, mockAst);
assert.ok(mockGetDeclaration.called);
assert.equal(mockGetDeclaration.args[0][0], 'x');
assert.deepEqual(mockInstance.identifiers, [
{
context: mockInstance.currentContext,
ast: mockAst,
declaration: 123
}
]);
assert.equal(mockIsState.args[0][0], 'trackIdentifiers');
});
test('works with ReturnStatement', () => {
const mockArgument = {};
let called = false;
let calledArgument = null;
const mockInstance = {
returnStatements: [],
scan: (argument) => {
called = true;
calledArgument = argument;
}
};
const mockAst = { type: 'ReturnStatement', argument: mockArgument };
FunctionTracer.prototype.scan.call(mockInstance, mockAst);
assert.ok(called);
assert.equal(calledArgument, mockArgument);
assert.equal(mockInstance.returnStatements[0], mockAst);
});
test('works with MemberExpression', () => {
const mockBody = {};
const mockProperty = {};
const mockPushState = sinon.spy();
const mockPopState = sinon.spy();
const mockScan = sinon.spy();
const mockInstance = {
scan: mockScan,
pushState: mockPushState,
popState: mockPopState,
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'MemberExpression', object: mockBody, property: mockProperty });
assert.ok(mockScan.called);
assert.equal(mockScan.args[0][0], mockBody);
assert.equal(mockScan.args[1][0], mockProperty);
assert.equal(mockPushState.args[0][0], 'memberExpression');
assert.equal(mockPopState.args[0][0], 'memberExpression');
});
test('works with ExpressionStatement', () => {
const mockExpression = {};
let called = false;
let calledExpression = null;
const mockInstance = {
scan: (body) => {
called = true;
calledExpression = body;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'ExpressionStatement', expression: mockExpression });
assert.ok(called);
assert.equal(calledExpression, mockExpression);
});
test('works with SequenceExpression', () => {
const mockExpression = {};
const mockExpressions = [mockExpression];
let called = false;
let calledExpression = null;
const mockInstance = {
scan: (body) => {
called = true;
calledExpression = body;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'SequenceExpression', expressions: mockExpressions });
assert.ok(called);
assert.equal(calledExpression, mockExpressions);
});
test('works with CallExpression', () => {
const mockArguments = {};
let called = false;
let calledArguments = null;
const mockCurrentContext = {};
const mockInstance = {
currentContext: mockCurrentContext,
functionCalls: [],
scan: (_arguments) => {
called = true;
calledArguments = _arguments;
}
};
const mockAst = { type: 'CallExpression', arguments: mockArguments };
FunctionTracer.prototype.scan.call(mockInstance, mockAst);
assert.ok(called);
assert.equal(calledArguments, mockArguments);
assert.deepEqual(mockInstance.functionCalls, [
{
context: mockCurrentContext,
ast: mockAst
}
]);
});
test('works with ArrayExpression', () => {
const mockElements = {};
let called = false;
let calledElements = null;
const mockInstance = {
scan: (elements) => {
called = true;
calledElements = elements;
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'ArrayExpression', elements: mockElements });
assert.ok(called);
assert.equal(calledElements, mockElements);
});
test('works with ConditionalExpression', () => {
const mockTest = {};
const mockAlternate = {};
const mockConsequent = {};
let called = false;
let calledArgs = [];
const mockInstance = {
scan: (arg) => {
called = true;
calledArgs.push(arg);
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'ConditionalExpression', test: mockTest, alternate: mockAlternate, consequent: mockConsequent });
assert.ok(called);
assert.deepEqual(calledArgs, [mockTest, mockConsequent, mockConsequent]);
});
test('works with SwitchStatement', () => {
const mockDiscriminant = {};
const mockCases = {};
let called = false;
let calledArgs = [];
const mockInstance = {
scan: (arg) => {
called = true;
calledArgs.push(arg);
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'SwitchStatement', discriminant: mockDiscriminant, cases: mockCases });
assert.ok(called);
assert.deepEqual(calledArgs, [mockDiscriminant, mockCases]);
});
test('works with SwitchCase', () => {
const mockTest = {};
const mockConsequent = {};
let called = false;
let calledArgs = [];
const mockInstance = {
scan: (arg) => {
called = true;
calledArgs.push(arg);
}
};
FunctionTracer.prototype.scan.call(mockInstance, { type: 'SwitchCase', test: mockTest, consequent: mockConsequent });
assert.ok(called);
assert.deepEqual(calledArgs, [mockTest, mockConsequent]);
});
test('does nothing with un-scan-ables', () => {
let called = false;
const mockInstance = {
scan: () => {
called = true;
}
};
[
'ThisExpression',
'Literal',
'DebuggerStatement',
'EmptyStatement',
'BreakStatement',
'ContinueStatement'
].forEach(type => {
FunctionTracer.prototype.scan.call(mockInstance, { type });
});
assert.ok(!called);
});
test('when called with fake type, throws', () => {
assert.throws(() => {
FunctionTracer.prototype.scan.call({}, { type: 'Made Up' });
});
});
test('can handle direct arrays', () => {
const mockBlockBody = {};
const mockProgramBody = {};
const asts = [
{ type: 'BlockStatement' },
{ type: 'Program' },
];
const calledAsts = [];
const mockInstance = {
scan: (ast) => {
calledAsts.push(ast);
}
};
FunctionTracer.prototype.scan.call(mockInstance, asts);
assert.deepEqual(calledAsts, asts);
});