Skip to content

Commit

Permalink
feat(JavaScript mutator): Add stryker-javascript-mutator package (#467)
Browse files Browse the repository at this point in the history
* Add stryker-javascript-mutator package that is compatible with modern JavaScript features such as arrow functions.
* Deprecate the `ES5Mutator`
* Remove `UnaryNotMutator` as it is already present in the `PrefixUnaryExpressionMutator`
* Make the `ConditionalExpressionMutator` also mutate to `true`

Fixes #429
  • Loading branch information
simondel authored Nov 24, 2017
1 parent 717b00c commit 06d6bac
Show file tree
Hide file tree
Showing 60 changed files with 1,082 additions and 53 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"private": true,
"devDependencies": {
"@types/babel-core": "^6.25.2",
"@types/chai-as-promised": "0.0.31",
"@types/chalk": "^0.4.28",
"@types/commander": "^2.9.0",
Expand Down
10 changes: 10 additions & 0 deletions packages/stryker-javascript-mutator/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
**/*
!*.d.ts
!bin/**
!src/**
src/**/*.map
src/**/*.ts
!src/**/*.d.ts
!readme.md
!LICENSE
!CHANGELOG.md
36 changes: 36 additions & 0 deletions packages/stryker-javascript-mutator/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Unit tests",
"program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceRoot}/test/helpers/**/*.js",
"${workspaceRoot}/test/unit/**/*.js"
],
"internalConsoleOptions": "openOnSessionStart"
}, {
"type": "node",
"request": "launch",
"name": "Integration tests",
"program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceRoot}/test/helpers/**/*.js",
"${workspaceRoot}/test/integration/**/*.js"
],
"internalConsoleOptions": "openOnSessionStart"
}
]
}
14 changes: 14 additions & 0 deletions packages/stryker-javascript-mutator/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"typescript.tsdk": "../../node_modules/typescript/lib",
"files.exclude": {
".git": true,
".tscache": true,
"**/*.js": {
"when": "$(basename).ts"
},
"**/*.d.ts": true,
"**/*.map": {
"when": "$(basename)"
}
}
}
18 changes: 18 additions & 0 deletions packages/stryker-javascript-mutator/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"taskName": "tsc-watch",
"type": "shell",
"command": "npm start",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
39 changes: 39 additions & 0 deletions packages/stryker-javascript-mutator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[![Build Status](https://travis-ci.org/stryker-mutator/stryker.svg?branch=master)](https://travis-ci.org/stryker-mutator/stryker)
[![NPM](https://img.shields.io/npm/dm/stryker-javascript-mutator.svg)](https://www.npmjs.com/package/stryker-javascript-mutator)
[![Node version](https://img.shields.io/node/v/stryker-javascript-mutator.svg)](https://img.shields.io/node/v/stryker-javascript-mutator.svg)
[![Gitter](https://badges.gitter.im/stryker-mutator/stryker.svg)](https://gitter.im/stryker-mutator/stryker?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![BCH compliance](https://bettercodehub.com/edge/badge/stryker-mutator/stryker)](https://bettercodehub.com/)

![Stryker](https://github.com/stryker-mutator/stryker/raw/master/stryker-80x80.png)

# Stryker JavaScript mutator

A mutator that supports JavaScript for [Stryker](https://stryker-mutator.github.io), the JavaScript Mutation testing framework. This plugin does not transpile any code. The code that the stryker-javascript-mutator gets should be executable in your environment (i.e. the stryker-javascript-mutator does not add support for Babel projects).

## Quickstart

First, install Stryker itself (you can follow the [quickstart on the website](http://stryker-mutator.github.io/quickstart.html))

Next, install this package:

```bash
npm install --save-dev stryker-javascript-mutator
```

Now open up your stryker.conf.js file and add the following components:

```javascript
mutator: 'javascript',
```

Now give it a go:

```bash
$ stryker run
```

### JavaScript Mutator

The `JavaScript Mutator` is a plugin to mutate JavaScript code. This is done using Babel without any plugins.

See [test code](https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-javascript-mutator/test/unit/mutator) to know which mutations are supported.
56 changes: 56 additions & 0 deletions packages/stryker-javascript-mutator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "stryker-javascript-mutator",
"version": "0.1.0",
"description": "A plugin for javascript projects using Stryker",
"main": "src/index.js",
"scripts": {
"start": "tsc -w",
"clean": "rimraf \"+(test|src)/**/*+(.d.ts|.js|.map)\" reports",
"prebuild": "npm run clean",
"build": "tsc -p .",
"postbuild": "tslint -p tsconfig.json",
"test": "nyc --reporter=html --report-dir=reports/coverage --check-coverage --lines 85 --functions 90 --branches 60 mocha \"test/helpers/**/*.js\" \"test/unit/**/*.js\" "
},
"repository": {
"type": "git",
"url": "https://github.com/stryker-mutator/stryker"
},
"engines": {
"node": ">=4"
},
"keywords": [
"stryker",
"stryker-plugin",
"javascript",
"stryker-mutator"
],
"bugs": {
"url": "https://github.com/stryker-mutator/stryker/issues"
},
"author": "Simon de Lang <[email protected]>",
"contributors": [
"Nico Jansen <[email protected]>",
"Niek te Grootenhuis <[email protected]>",
"Thomas Peters <[email protected]>",
"Sander Koenders <[email protected]>"
],
"homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-javascript-mutator#readme",
"license": "Apache-2.0",
"dependencies": {
"babel-core": "^6.26.0",
"babel-generator": "^6.26.0",
"babylon": "^6.18.0",
"log4js": "^1.1.1",
"tslib": "^1.8.0"
},
"devDependencies": {
"@types/babel-generator": "^6.25.1",
"@types/babylon": "^6.16.2",
"stryker-api": "^0.11.0",
"stryker-mutator-specification": "^0.1.0"
},
"peerDependencies": {
"stryker-api": "^0.11.0",
"typescript": "^2.5.3"
}
}
65 changes: 65 additions & 0 deletions packages/stryker-javascript-mutator/src/JavaScriptMutator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as babel from 'babel-core';
import { getLogger } from 'log4js';
import { Mutator, Mutant } from 'stryker-api/mutant';
import { File, FileKind, TextFile } from 'stryker-api/core';
import { Config } from 'stryker-api/config';
import BabelParser from './helpers/BabelParser';
import copy from './helpers/copy';
import NodeMutatorFactory from './NodeMutatorFactory';
import NodeMutator from './mutators/NodeMutator';

function defaultMutators(): NodeMutator[] {
return NodeMutatorFactory.instance().knownNames().map(name => NodeMutatorFactory.instance().create(name, undefined));
}

export default class JavaScriptMutator implements Mutator {
private log = getLogger(JavaScriptMutator.name);

constructor(config: Config, private mutators: NodeMutator[] = defaultMutators()) {
}

public mutate(inputFiles: File[]): Mutant[] {
const mutants: Mutant[] = [];

inputFiles.filter(i => i.kind === FileKind.Text && i.mutated).forEach((file: TextFile) => {
const ast = BabelParser.getAst(file.content);
const baseAst = copy(ast, true);
BabelParser.removeUseStrict(baseAst);

BabelParser.getNodes(ast).forEach(node => {
this.mutators.forEach(mutator => {
let mutatedNodes = mutator.mutate(node, copy);

if (mutatedNodes) {
const newMutants = this.generateMutants(mutatedNodes, baseAst, file, mutator.name);
newMutants.forEach(mutant => mutants.push(mutant));
}
});
});
});

return mutants;
}

private generateMutants(nodes: babel.types.Node[], ast: babel.types.File, file: TextFile, mutatorName: string) {
const mutants: Mutant[] = [];

nodes.forEach(node => {
const replacement = BabelParser.generateCode(ast, node);
if (replacement) {
const range: [number, number] = [node.start, node.end];

const mutant = {
mutatorName,
fileName: file.name,
range,
replacement
};
this.log.trace(`Generated mutant for mutator ${mutatorName} in file ${file.name} with replacement code "${replacement}"`);
mutants.push(mutant);
}
});

return mutants;
}
}
23 changes: 23 additions & 0 deletions packages/stryker-javascript-mutator/src/NodeMutatorFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Factory } from 'stryker-api/core';
import NodeMutator from './mutators/NodeMutator';

namespace NodeMutatorFactory {
/**
* Represents a Factory for TestFrameworks.
*/
class NodeMutatorFactory extends Factory<void, NodeMutator> {
constructor() {
super('nodeMutator');
}
}
const nodeMutatorFactoryInstance = new NodeMutatorFactory();

/**
* Returns the current instance of the MutatorFactory.
*/
export function instance() {
return <Factory<void, NodeMutator>>nodeMutatorFactoryInstance;
}
}

export default NodeMutatorFactory;
40 changes: 40 additions & 0 deletions packages/stryker-javascript-mutator/src/helpers/BabelParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as babel from 'babel-core';
import * as babylon from 'babylon';
import generate from 'babel-generator';
import { NodePath } from 'babel-traverse';

export default class BabelParser {
static getAst(code: string): babel.types.File {
return babylon.parse(code);
}

static getNodes(ast: babel.types.File): babel.types.Node[] {
const nodes: babel.types.Node[] = [];

babel.traverse(ast, {
enter(path: NodePath<babel.types.Node>) {
const node = path.node;
Object.freeze(node);
nodes.push(node);
}
});

return nodes;
}

static generateCode(ast: babel.types.File, node: babel.Node) {
ast.program.body = [node as any];
return generate(ast).code;
}

static removeUseStrict(ast: babel.types.File) {
if (ast.program.directives) {
const directives = ast.program.directives;
directives.forEach((directive, index) => {
if (directive.value.value === 'use strict') {
directives.splice(index, 1);
}
});
}
}
}
13 changes: 13 additions & 0 deletions packages/stryker-javascript-mutator/src/helpers/NodeGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { types } from 'babel-core';

export default class NodeGenerator {
static createBooleanLiteralNode(originalNode: types.Node, value: boolean): types.BooleanLiteral {
return {
start: originalNode.start,
end: originalNode.end,
loc: originalNode.loc,
type: 'BooleanLiteral',
value: value
};
}
}
9 changes: 9 additions & 0 deletions packages/stryker-javascript-mutator/src/helpers/copy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as _ from 'lodash';

export default <T>(obj: T, deep?: boolean) => {
if (deep) {
return _.cloneDeep(obj);
} else {
return _.clone(obj);
}
};
5 changes: 5 additions & 0 deletions packages/stryker-javascript-mutator/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { MutatorFactory } from 'stryker-api/mutant';
import JavaScriptMutator from './JavaScriptMutator';
require('./mutators');

MutatorFactory.instance().register('javascript', JavaScriptMutator);
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { types } from 'babel-core';
import NodeMutator from './NodeMutator';

/**
* Represents a mutator which can remove the content of an array's elements.
*/
export default class ArrayLiteralMutator implements NodeMutator {
name = 'ArrayLiteral';

mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): void | types.Node[] {
let nodes: types.Node[] = [];

if (types.isArrayExpression(node) && node.elements.length > 0) {
let mutatedNode = copy(node);
mutatedNode.elements = [];
nodes.push(mutatedNode);
}

return nodes;
}
}
Loading

0 comments on commit 06d6bac

Please sign in to comment.