Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(JavaScript mutator): Add stryker-javascript-mutator package #467

Merged
merged 44 commits into from
Nov 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
70acf07
feat(ES6 mutator): Add initial ES6 mutator
simondel Oct 20, 2017
019e21f
fix(mutators): Register mutators with a require
simondel Oct 20, 2017
62dec11
Test(ES6Mutator): Test if the right mutants are generated
simondel Oct 20, 2017
2831a63
fix(BinaryOperatorMutator): Set the name property
simondel Oct 20, 2017
8cfa384
refactor(ES6Mutator): Extract Copy function
simondel Oct 20, 2017
5e7364e
fix(BooleanSubstitutionMutator): Set range to parent node
simondel Oct 20, 2017
c302546
test(BabelParser): Add test to see if comments are kept
simondel Oct 20, 2017
53fca50
refactor(IdentifiedNode): Remove IdentifiedNode
simondel Oct 20, 2017
8b19b5b
refactor(stryker-javascript-mutator): Rename ES6Mutator to JavaScript…
simondel Oct 20, 2017
314fa3f
feat(ES5Mutator): Add deprecation warning
simondel Oct 20, 2017
102deaf
Merge branch 'master' into stryker-es6-mutator
simondel Oct 20, 2017
5c99791
fix(ES5Mutator): Use different deprecation warning
simondel Oct 21, 2017
dfbff1a
Merge branch 'master' into stryker-es6-mutator
simondel Oct 21, 2017
946c71b
Merge branch 'master' into stryker-es6-mutator
nicojs Nov 8, 2017
8241157
refactor(javascript-mutator): Inject mutators in JavaScriptMutator
nicojs Nov 9, 2017
665878b
rename copy.ts -> Copy.ts part 1
nicojs Nov 9, 2017
1a6a0c3
rename copy.ts -> Copy.ts part 2
nicojs Nov 9, 2017
5d2bded
Set coverage goal to 60% (temporarily)
nicojs Nov 9, 2017
ca0f3e9
Merge branch 'master' into stryker-es6-mutator
nicojs Nov 14, 2017
4373fb3
Merge branch 'master' into stryker-es6-mutator
nicojs Nov 14, 2017
6ee00ab
Merge branch 'master' into stryker-es6-mutator
simondel Nov 17, 2017
9f1db6c
Merge branch 'stryker-es6-mutator' of https://github.com/stryker-muta…
simondel Nov 17, 2017
710127f
Merge branch 'master' into stryker-es6-mutator
simondel Nov 24, 2017
4edce3a
test(Mutators): Add tests
simondel Nov 24, 2017
e03ecb3
feat(Whilestatementmutator): Add whilestatementmutator
simondel Nov 24, 2017
f8544a4
Fix(WhileStatementMutatorSpec): removed unused import statement
Niek-tg Nov 24, 2017
8c7086f
fix(Array tests): Use correct implementation
simondel Nov 24, 2017
79f76e2
Fix(JavascriptMutatorSpec): Replaced BinaryOperator with BinaryExpres…
Niek-tg Nov 24, 2017
9f29609
fix(BlockMutatorSpec): Don't use fully anonymous functions
simondel Nov 24, 2017
a6c64da
Refactor(BinaryExpressionMutator): added isLogicalExpression and remo…
Niek-tg Nov 24, 2017
1ea8cd9
Merge branch 'stryker-es6-mutator' of https://github.com/stryker-muta…
Niek-tg Nov 24, 2017
26c6d6e
feat(package): Add new dependencies
Nov 24, 2017
9ec7a57
Refactor(JavaScriptMutator): Add public keyword
Nov 24, 2017
b8382d6
Feat(AST): Alter parser
Nov 24, 2017
9d1cf5e
Chore(Merge): Merge with upstream
Nov 24, 2017
52bebd6
Fix(TsLint): Fix TsLint errors
Nov 24, 2017
bd84491
refactored RemovConditionalMutator, seperated into multiple mutators
Nov 24, 2017
e5964e6
refactor(Unary and UpdateOperatorMutator): Use Pre and postfix unary …
simondel Nov 24, 2017
bc3a181
Merge branch 'stryker-es6-mutator' of https://github.com/stryker-muta…
simondel Nov 24, 2017
ff03b45
fixed merge conflict
Nov 24, 2017
7e5e519
fix(dependencies): Fix stryker-api version
simondel Nov 24, 2017
85fd540
fix(BooleanSubstitutionMutator): Remove duplicate unarynotmutator
simondel Nov 24, 2017
20c5a2f
chore: review comments
simondel Nov 24, 2017
39af943
chore: Add contributors
simondel Nov 24, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <simondelang@gmail.com>",
"contributors": [
"Nico Jansen <jansennico@gmail.com>",
"Niek te Grootenhuis <ntegro@hotmail.com>",
"Thomas Peters <thomaspeters4@outlook.com>",
"Sander Koenders <sanderkoenders@gmail.com>"
],
"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