Skip to content

Commit

Permalink
incremental commit: using comments to suppress mutators
Browse files Browse the repository at this point in the history
  • Loading branch information
bttmly committed Aug 4, 2016
1 parent 32a8eaa commit ba2c459
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 19 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
test: lint
NODE_ENV=testing ./node_modules/.bin/_mocha ./test/**/*.js
NODE_ENV=testing ./node_modules/.bin/_mocha ./test --recursive

test-bail: lint
NODE_ENV=testing ./node_modules/.bin/_mocha ./test/**/*.js --bail
NODE_ENV=testing ./node_modules/.bin/_mocha ./test --bail --recursive

lint:
./node_modules/.bin/eslint ./src/**/*.js
Expand Down
79 changes: 79 additions & 0 deletions src/comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import R = require("ramda");

const PERTURB_ENABLE = "perturb-enable:";
const PERTURB_DISABLE = "perturb-disable:";

interface Operator {
type: string;
name: string;
}

interface Comment {
type: string;
value: string;
range: number[];
}

interface CommentedNode extends ESTree.Node {
leadingComments?: Comment[];
trailingComments?: Comment[];
}

function applyNodeComments (_node: ESTree.Node, disabledSet: Set<string>) {
const node = <CommentedNode>_node;
R.pipe(
_getComments,
R.chain(_extractOperators),
R.forEach(_applyOperator(disabledSet))
)(node);
}

const _applyOperator = R.curry(function (set: Set<string>, op: Operator) {
switch (op.type) {
case "enable": {
console.log("ENABLE", op.name)
return set.add(op.name);
}
case "disable": {
console.log("DISABLE", op.name);
return set.delete(op.name);
}
}
});

function _getComments (node: CommentedNode): Comment[] {
return [].concat(
node.leadingComments || [],
node.trailingComments || []
);
}

function _extractOperators (c: Comment): Operator[] {
const value = c.value.trim();

console.log("RAW VALUE", value);

let type;
if (value.startsWith(PERTURB_ENABLE)) {
type = "enable";
} else if (value.startsWith(PERTURB_DISABLE)) {
type = "disable";
}
if (type == null) return null;

return R.pipe(
R.split(":"),
R.prop("1"),
R.split(","),
R.filter(Boolean),
R.map(R.trim),
R.map(name => ({ type, name: String(name) }))
)(value);
}

export = {
_getComments,
_extractOperators,
_applyOperator,
applyNodeComments,
};
8 changes: 7 additions & 1 deletion src/make-mutants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import shouldSkip = require("./skippers");
import updateIn = require("./util/update-in");
const { getMutatorsForNode, hasAvailableMutations } = require("./mutators");

const PERTURB_ENABLE = "perturb-enable:";
const PERTURB_DISABLE = "perturb-disable:";

const ESPRIMA_SETTINGS = {
loc: true,
comment: true,
attachComment: true,
};

const FS_SETTINGS = {
Expand All @@ -21,7 +25,7 @@ const FS_SETTINGS = {
function makeMutants (match: Match): Mutant[] {
const { source, tests } = match;
const { ast, code } = parse(source);
const paths: Path[] = getMutationPaths(ast).map(p => p.map(String));
const paths: Path[] = getMutationPaths(ast);

// we regenerate the source code here to make it easy for diffing
const originalSourceCode = escodegen.generate(ast);
Expand Down Expand Up @@ -60,6 +64,7 @@ function makeMutants (match: Match): Mutant[] {

type Path = string[];


function getMutationPaths (ast: ESTree.Node) {
const mutationPaths: Path[] = [];
estraverse.traverse(ast, {
Expand Down Expand Up @@ -98,6 +103,7 @@ function parse (source: string) {
}
}

const last = arr => arr[arr.length - 1];
const toArray = x => Array.isArray(x) ? x : [x];

export = makeMutants;
12 changes: 7 additions & 5 deletions src/mutators/tweak-arguments.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import R = require("ramda");
import S = require("./_syntax");
import dropItem = require("../util/drop-item");
import dropEachOfProp = require("../util/drop-each-of-prop");

// drops each argument to a function/method call in turn
// input: `fn(a, b, c)`
// output: [`fn(b, c)`, `fn(a, c)`, `fn(a, b)`]

export = <MutatorPlugin>{
// drops the first declared element in an array literal
// `['a', 'b']` => `['a']`
name: "tweak-arguments",
nodeTypes: [S.CallExpression],
filter: function (node) {
return R.path(["arguments", "length"], node) !== 0;
},
mutator: function (node) {
return dropItem(node, "arguments", "first");
mutator: function (node): ESTree.Node[] {
return dropEachOfProp("arguments", node);
},
};
6 changes: 2 additions & 4 deletions src/runners/fork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path = require("path");
import os = require("os");
import cp = require("child_process");

import runMutant = require("../util/run-mutant");
import runnerUtils = require("./utils");

// TODO -- make configurable
Expand Down Expand Up @@ -65,10 +66,7 @@ async function childRunner (name) {

const mutant: Mutant = require(process.argv[2])
const runner: RunnerPlugin = require("./")(name);

const before = await runner.setup(mutant);
const result: RunnerResult = await runner.run(mutant);
await runner.cleanup(mutant, before);
const result = await runMutant(runner, mutant);

process.send(JSON.stringify({
error: runnerUtils.makeErrorSerializable(result.error)
Expand Down
3 changes: 3 additions & 0 deletions src/runners/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export = {
fs.writeFileSync(m.sourceFile, m.originalSourceCode);
},

// TODO: we can optimize this in various ways, if it seems to be a bottleneck.
// Obviously it only really matters for in-process runners, but anecdotally a
// full cache flush seems to slow down those runners by 20-30%
clearRequireCache () {
Object.keys(require.cache).forEach(k => delete require.cache[k]);
},
Expand Down
11 changes: 6 additions & 5 deletions src/util/run-mutant.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export = async function runMutant (runner: RunnerPlugin, mutant: Mutant) {
const before = await runner.setup(mutant);
const result: RunnerResult = await runner.run(mutant);
await runner.cleanup(mutant, before);
return result;
export = function runMutant (runner: RunnerPlugin, mutant: Mutant) {
return runner.setup(mutant)
.then(function (before) {
return runner.run(mutant)
.finally(() => runner.cleanup(mutant, before))
});
}
67 changes: 67 additions & 0 deletions test/comments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const expect = require("expect");
const esprima = require("esprima");
const comments = require("../built/comments");

const helpers = require("./helpers");

const ESPRIMA_OPTIONS = {
attachComment: true,
comments: true,
loc: true,
};

function createTest (obj) {
it(obj.title, function () {
const node = helpers.nodeFromCode(obj.code);
const set = obj.set || new Set();
const expected = obj.expected;
comments.applyNodeComments(node, set);
expect([...set]).toEqual(expected);
});
}


describe("comments", function () {

describe("comments", function () {

createTest({
title: "enable: works for leading line comments",
expected: "abc".split(""),
code: `
// perturb-enable: a,,b,c
const x = 1;
`,
});

createTest({
title: "enable: works for trailing line comments",
expected: "abc".split(""),
code: `
const x = 1;
// perturb-enable: a,,b,c
`,
});

createTest({
title: "enable: works for leading and trailing comments",
expected: "abcd".split(""),
code: `
// perturb-enable: a,,b,c
const x = 1;
// perturb-enable: d
`,
});

createTest({
title: "disable: works for leading and trailing comments",
expected: ["b"],
code: `
// perturb-enable: a,b
const x = 1;
// perturb-disable: a
`,
});
});

});
8 changes: 7 additions & 1 deletion test/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
const esprima = require("esprima");
const R = require("ramda");

const ESPRIMA_OPTIONS = {
attachComment: true,
comments: true,
loc: true,
};

const { getMutatorByName } = require("../built/mutators");
const { Syntax } = require("estraverse");

Expand All @@ -21,7 +27,7 @@ function makeNodeOfType (type, props = {}) {
}

function nodeFromCode (code) {
const ast = esprima.parse(code);
const ast = esprima.parse(code, ESPRIMA_OPTIONS);
if (ast.body.length !== 1) {
throw new Error("Rendered AST did not have exactly one node");
}
Expand Down
2 changes: 1 addition & 1 deletion test/mutators/tweak-object-literal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { nodeFromCode, applyMutation } = require("../helpers");

describe("tweak-object-literal", function () {
it("removes the first property of an object literal", function () {
const node = nodeFromCode("x = {a: 1, b: 2, c: 3}").expression.right;
const node = nodeFromCode("({a: 1, b: 2, c: 3})").expression;
expect(node.type).toEqual("ObjectExpression");
const nodes = applyMutation(node, "tweak-object-literal");

Expand Down

0 comments on commit ba2c459

Please sign in to comment.