├── .appveyor.yml ├── .gitignore ├── .jsdoc.json ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── mutode ├── docs ├── Mutators.html ├── Mutode.html ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.svg │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.svg │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.svg │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.svg │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.svg │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.svg │ ├── OpenSans-Regular-webfont.woff │ ├── OpenSans-Semibold-webfont.eot │ ├── OpenSans-Semibold-webfont.svg │ ├── OpenSans-Semibold-webfont.ttf │ ├── OpenSans-Semibold-webfont.woff │ ├── OpenSans-SemiboldItalic-webfont.eot │ ├── OpenSans-SemiboldItalic-webfont.svg │ ├── OpenSans-SemiboldItalic-webfont.ttf │ └── OpenSans-SemiboldItalic-webfont.woff ├── global.html ├── index.html ├── module-Mutant%20Runner.html ├── module-MutantRunner.html ├── module-Mutators.ConditionalsBoundaryMutator.html ├── module-Mutators.html ├── module-Mutators.module_Conditionals%20Boundary%20Mutator.html ├── module-Mutators.module_ConditionalsBoundaryMutator.html ├── mutantRunner.js.html ├── mutators_booleanLiteralsMutator.js.html ├── mutators_conditionalsBoundaryMutator.js.html ├── mutators_deletionMutator.js.html ├── mutators_incrementsMutator.js.html ├── mutators_invertNegativesMutator.js.html ├── mutators_mathMutator.js.html ├── mutators_negateConditionalsMutator.js.html ├── mutators_numericLiteralsMutator.js.html ├── mutators_removeArrayElementsMutator.js.html ├── mutators_removeConditionalsMutator.js.html ├── mutators_removeFuncCallArgsMutator.js.html ├── mutators_removeFuncParamsMutator.js.html ├── mutators_removeFunctionsMutator.js.html ├── mutators_removeLinesMutator.js.html ├── mutators_removeObjPropsMutator.js.html ├── mutators_removeSwitchCasesMutator.js.html ├── mutators_returnValuesMutator.js.html ├── mutators_stringLiteralsMutator.js.html ├── mutators_switchCasesMutator.js.html ├── mutode.js.html ├── namespace_Mutators.module_ConditionalsBoundaryMutator.html ├── scripts │ ├── linenumber.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js │ └── script.js └── styles │ ├── jsdoc-default.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css ├── example-module ├── package.json ├── src │ ├── discarded.js │ ├── killed-dep.js │ ├── killed.js │ ├── no-ast.js │ └── survived.js └── test.js ├── greenkeeper.json ├── no-tests-module ├── index.js └── package.json ├── package-lock.json ├── package.json ├── src ├── mutantRunner.js ├── mutators │ ├── booleanLiteralsMutator.js │ ├── conditionalsBoundaryMutator.js │ ├── incrementsMutator.js │ ├── invertNegativesMutator.js │ ├── mathMutator.js │ ├── negateConditionalsMutator.js │ ├── numericLiteralsMutator.js │ ├── removeArrayElementsMutator.js │ ├── removeConditionalsMutator.js │ ├── removeFuncCallArgsMutator.js │ ├── removeFuncParamsMutator.js │ ├── removeFunctionsMutator.js │ ├── removeLinesMutator.js │ ├── removeObjPropsMutator.js │ ├── removeSwitchCasesMutator.js │ └── stringLiteralsMutator.js ├── mutode.js └── util │ └── lineDiff.js └── test ├── exampleModule.js ├── noTestsModule.js └── test.js /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the LTS version of this Node.js version 2 | environment: 3 | nodejs_version: "LTS" 4 | 5 | # Install scripts. (runs after repo cloning) 6 | install: 7 | # Get the latest stable version of Node.js or io.js 8 | - ps: Install-Product node $env:nodejs_version 9 | # install modules 10 | - npm install 11 | 12 | # Post-install test scripts. 13 | test_script: 14 | # Output useful info for debugging. 15 | - node --version 16 | - npm --version 17 | # run tests 18 | - npm test 19 | 20 | # Don't actually build. 21 | build: off 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | out 4 | gen### Node template 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | .grunt 18 | bower_components 19 | .lock-wscript 20 | build/Release 21 | node_modules/ 22 | jspm_packages/ 23 | typings/ 24 | .npm 25 | .eslintcache 26 | .node_repl_history 27 | *.tgz 28 | .yarn-integrity 29 | .env 30 | example-module/.mutode 31 | example-module/woot.js 32 | no-tests-module/.mutode 33 | .coveralls.yml 34 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": ["jsdoc"] 5 | }, 6 | "source": { 7 | "include": [ "./src", "README.md" ] 8 | }, 9 | "plugins": ["plugins/markdown"], 10 | "opts": { 11 | "encoding": "utf8", 12 | "destination": "./docs", 13 | "recurse": true, 14 | "template": "node_modules/minami" 15 | }, 16 | "templates": { 17 | "cleverLinks": false, 18 | "monospaceLinks": true, 19 | "useLongnameInNav": false, 20 | "showInheritedInNav": true 21 | } 22 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !src/**/* 3 | !bin/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'lts/*' 5 | - 'node' 6 | install: 7 | - npm install -g npm@latest 8 | - npm ci 9 | cache: 10 | directories: 11 | - "$HOME/.npm" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Diego Rodríguez Baquero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mutode [![npm](https://img.shields.io/npm/v/mutode.svg)](http://npmjs.com/package/mutode) [![npm](https://img.shields.io/npm/dm/mutode.svg)](http://npmjs.com/package/mutode) [![npm](https://img.shields.io/npm/l/mutode.svg)](LICENSE) 2 | 3 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 4 | [![Build Status](https://travis-ci.org/TheSoftwareDesignLab/mutode.svg?branch=master)](https://travis-ci.org/TheSoftwareDesignLab/mutode) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/ulp8cq3aq2bng6he/branch/master?svg=true)](https://ci.appveyor.com/project/DiegoRBaquero/mutode/branch/master) 6 | [![Coverage Status](https://coveralls.io/repos/github/TheSoftwareDesignLab/mutode/badge.svg?branch=master)](https://coveralls.io/github/TheSoftwareDesignLab/mutode?branch=master) 7 | [![Greenkeeper badge](https://badges.greenkeeper.io/TheSoftwareDesignLab/mutode.svg)](https://greenkeeper.io/) 8 | 9 | Mutation testing for Node.js and JavaScript. 10 | 11 | **Mutode** generates mutants (small changes of code) and runs your tests. If the tests fail, it means the mutant was detected and **killed**; if your tests pass, it means the mutant **survived** and your tests can be improved. 12 | 13 | [**Watch the demo video**](https://www.youtube.com/watch?v=DILzHOljFj0&feature=youtu.be) and 14 | [**Check the slides of the Node Summit 2018 talk**](https://speakerdeck.com/diegorbaquero/beyond-code-coverage-mutation-testing-tests-for-your-tests) 15 | 16 | > "It's like a test for your tests!" - @mappum 17 | 18 | > "Higher order testing: automated testing for your unit tests" - @albertomiranda 19 | 20 | ## Publications 21 | 22 | Read the tool demo paper [*"Mutode: generic JavaScript and Node.js mutation testing tool"*](https://dl.acm.org/citation.cfm?id=3229504). In Proceedings of the 27th ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA 2018) 23 | 24 | Read the thesis proposal [**here**](https://docs.google.com/document/d/1V6U-ahLq6faCbtP0DtKukzdnsUC2ZBsL1LWEJvkqUiE/edit?usp=sharing) 25 | 26 | 27 | ## Install 28 | 29 | **Requires Node 8+** 30 | 31 | Globally: 32 | 33 | ```sh 34 | npm i -g mutode 35 | ``` 36 | 37 | Locally as a dev dependency: 38 | 39 | ```sh 40 | npm i -D mutode 41 | ``` 42 | 43 | ## Use 44 | 45 | Globally: 46 | 47 | ```sh 48 | mutode [options] [paths] 49 | ``` 50 | 51 | Locally with `npx`: 52 | 53 | ```sh 54 | npx mutode [options] [paths] 55 | ``` 56 | 57 | Locally with a package.json script: 58 | 59 | ``` 60 | { 61 | ... 62 | "scripts": { 63 | "test: "my test command", 64 | "mutode": "mutode [options] [paths]" 65 | } 66 | ... 67 | } 68 | ``` 69 | 70 | **Options**: 71 | 72 | ``` 73 | Usage: mutode [options] [paths] 74 | 75 | Options: 76 | --concurrency, -c Concurrency of mutant runners [number] [default: 4] 77 | --mutators, -m Specific mutators to load (space separated) 78 | [array] [choices: "booleanLiterals", "conditionalsBoundary", "increments", 79 | "invertNegatives", "math", "negateConditionals", "numericLiterals", 80 | "removeArrayElements", "removeConditionals", "removeFuncCallArgs", 81 | "removeFuncParams", "removeFunctions", "removeLines", "removeObjProps", 82 | "removeSwitchCases", "stringLiterals"] 83 | -h, --help Show help [boolean] 84 | -v, --version Show version number [boolean] 85 | ``` 86 | 87 | ## Docs 88 | 89 | - Current supported mutation operators are available [**here**](https://thesoftwaredesignlab.github.io/mutode/module-Mutators.html) 90 | - General documentation is available [**here**](https://thesoftwaredesignlab.github.io/mutode/) 91 | 92 | ## Videos 93 | 94 | - [Demo](https://www.youtube.com/watch?v=DILzHOljFj0&feature=youtu.be) 95 | 96 | ## License 97 | MIT Copyright © Diego Rodríguez Baquero 98 | -------------------------------------------------------------------------------- /bin/mutode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const globby = require('globby') 4 | const os = require('os') 5 | 6 | const argv = require('yargs') 7 | .usage('Usage: $0 [options] [paths]') 8 | .option('concurrency', { 9 | alias: 'c', 10 | default: os.cpus().length, 11 | describe: 'Concurrency of mutant runners', 12 | type: 'number' 13 | }) 14 | .option('mutators', { 15 | alias: 'm', 16 | describe: 'Specific mutators to load (space separated)', 17 | type: 'array', 18 | choices: globby.sync('../src/mutators/*', { cwd: __dirname }).map(f => f.split('/').pop().replace('Mutator.js', '')) 19 | }) 20 | .help('h') 21 | .alias('h', 'help') 22 | .alias('v', 'version') 23 | .argv 24 | 25 | const Mutode = require('../src/mutode') 26 | 27 | const mutator = new Mutode({ paths: argv._, concurrency: argv.concurrency, mutators: argv.mutators }) 28 | 29 | mutator.run().catch(e => { 30 | console.error(e.message) 31 | process.exit(1) 32 | }) 33 | -------------------------------------------------------------------------------- /docs/Mutators.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mutators - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

Mutators

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 |

45 | Mutators 46 |

47 | 48 | 49 |
50 | 51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
Source:
87 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 | 99 | 100 | 101 | 102 |
103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |
122 | 123 |
124 | 125 | 126 | 127 | 128 |
129 | 130 |
131 | 132 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSoftwareDesignLab/mutode/2b7d7fa74a7930337f40311021037caf7a28569b/docs/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/global.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Global - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

Global

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 |

45 | 46 |

47 | 48 | 49 |
50 | 51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 | 94 | 95 | 96 | 97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |

Methods

113 | 114 | 115 | 116 |
117 | 118 | 119 | 120 |

what()

121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 |
131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
Source:
158 |
161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 |
169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |
193 | 194 | 195 | 196 | 197 | 198 | 199 |
200 | 201 |
202 | 203 | 204 | 205 | 206 |
207 | 208 |
209 | 210 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /docs/module-Mutant%20Runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mutant Runner - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

Mutant Runner

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 |
47 | 48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | 76 |
77 | 78 | 79 | 80 | 81 |
82 | 83 |
84 | 85 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/module-MutantRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MutantRunner - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

MutantRunner

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 |
51 |
52 | 53 | 54 |

Runs a given mutant in a free worker, logging one of the possible results (survived, killed or discarded) and the time of execution.

55 |

Execution is done with the npm test command inside the worker's directory

56 | 57 | 58 | 59 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
Source:
96 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 |
131 | 132 | 133 |
134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
153 | 154 |
155 | 156 | 157 | 158 | 159 |
160 | 161 |
162 | 163 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /docs/module-Mutators.ConditionalsBoundaryMutator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ConditionalsBoundaryMutator - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

ConditionalsBoundaryMutator

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 |

45 | Mutators. 46 | 47 | ConditionalsBoundaryMutator 48 |

49 | 50 | 51 |
52 | 53 |
54 |
55 | 56 | 57 |

The conditionals boundary mutator replaces the relational operators <, <=, >, >= with their boundary counterpart as per the table below.

58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
OriginalMutant
<>=
72 | 73 | 74 | 75 | 76 | 77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
Source:
105 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
116 | 117 | 118 | 119 | 120 |
121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
140 | 141 |
142 | 143 | 144 | 145 | 146 |
147 | 148 |
149 | 150 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /docs/module-Mutators.module_Conditionals%20Boundary%20Mutator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Conditionals Boundary Mutator - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

Conditionals Boundary Mutator

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 |
51 |
52 | 53 | 54 |

The conditionals boundary mutator replaces the relational operators <, <=, >, >= with their boundary counterpart as per the table below.

55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
OriginalMutant
<>=
69 | 70 | 71 | 72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
Source:
109 |
112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
144 | 145 | 146 |
147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |
166 | 167 |
168 | 169 | 170 | 171 | 172 |
173 | 174 |
175 | 176 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /docs/module-Mutators.module_ConditionalsBoundaryMutator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ConditionalsBoundaryMutator - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

ConditionalsBoundaryMutator

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 |
51 |
52 | 53 | 54 |

The conditionals boundary mutator replaces the relational operators <, <=, >, >= with their boundary counterpart as per the table below.

55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
OriginalMutant
<>=
69 | 70 | 71 | 72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
Source:
109 |
112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
144 | 145 | 146 |
147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |
166 | 167 |
168 | 169 | 170 | 171 | 172 |
173 | 174 |
175 | 176 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /docs/mutantRunner.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutantRunner.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutantRunner.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const chalk = require('chalk')
 43 | const spawn = require('child_process').spawn
 44 | const Debug = require('debug')
 45 | const fs = require('fs')
 46 | const path = require('path')
 47 | const terminate = require('terminate')
 48 | 
 49 | /**
 50 |  * @module MutantRunner
 51 |  * @description Runs a given mutant in a free worker, logging one of the possible results (survived, killed or discarded) and the time of execution.
 52 |  *
 53 |  * Execution is done with the `npm test` command inside the worker's directory
 54 |  */
 55 | module.exports = function MutantRunner ({mutodeInstance, filePath, contentToWrite, log}) {
 56 |   const debug = Debug(`mutants:${filePath}`)
 57 |   return async index => {
 58 |     await new Promise(resolve => {
 59 |       const startTime = process.hrtime()
 60 |       fs.writeFileSync(`.mutode/mutode-${mutodeInstance.id}-${index}/${filePath}`, contentToWrite)
 61 |       const child = spawn(mutodeInstance.npmCommand, ['test'], {cwd: path.resolve(`.mutode/mutode-${mutodeInstance.id}-${index}`)})
 62 | 
 63 |       child.stderr.on('data', data => {
 64 |         debug(data.toString())
 65 |       })
 66 | 
 67 |       let timedout = false
 68 |       const timeout = setTimeout(() => {
 69 |         terminate(child.pid)
 70 |         timedout = true
 71 |       }, mutodeInstance.timeout).unref()
 72 | 
 73 |       child.on('exit', (code, signal) => {
 74 |         const endTime = process.hrtime(startTime)
 75 |         const endTimeMS = (endTime[0] * 1e3 + endTime[1] / 1e6).toFixed(0)
 76 |         const timeDiff = chalk.gray(`${endTimeMS} ms`)
 77 |         clearTimeout(timeout)
 78 |         if (code === 0) {
 79 |           console.log(`${log}\t${chalk.bgRed('survived')} ${timeDiff}`)
 80 |           mutodeInstance.survived++
 81 |         } else if (signal || timedout) {
 82 |           console.log(`${log}\t${chalk.bgBlue('discarded (timeout)')} ${timeDiff}`)
 83 |           mutodeInstance.discarded++
 84 |         } else {
 85 |           console.log(`${log}\t${chalk.bgGreen('killed')} ${timeDiff}`)
 86 |           mutodeInstance.killed++
 87 |         }
 88 |         // console.log('exit', code)
 89 |         resolve()
 90 |       })
 91 |     })
 92 |   }
 93 | }
 94 | 
95 |
96 |
97 | 98 | 99 | 100 | 101 |
102 | 103 |
104 | 105 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/mutators_booleanLiteralsMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/booleanLiteralsMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/booleanLiteralsMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
43 | const debug = require('debug')('mutode:booleanLiteralsMutator')
44 | 
45 | const mutantRunner = require('../mutantRunner')
46 | const lineDiff = require('../util/lineDiff')
47 | 
48 | /**
49 |  * @description Mutates boolean literals values.
50 |  * Boolean literals are mutated to their negative.
51 |  * @function booleanLiteralsMutator
52 |  * @memberOf module:Mutators
53 |  */
54 | module.exports = async function booleanLiteralsMutator ({mutodeInstance, filePath, lines, queue, ast}) {
55 |   debug('Running boolean literals mutator on %s', filePath)
56 | 
57 |   walk.simple(ast, {
58 |     BooleanLiteral (node) {
59 |       const line = node.loc.start.line
60 |       const lineContent = lines[line - 1]
61 | 
62 |       const mutantLineContent = lineContent.substr(0, node.loc.start.column) +
63 |         !node.value +
64 |         lineContent.substr(node.loc.end.column)
65 | 
66 |       const mutantId = ++mutodeInstance.mutants
67 |       const diff = lineDiff(lineContent, mutantLineContent)
68 |       const log = `MUTANT ${mutantId}:\tBLM Line ${line}:\t${diff}...`
69 |       debug(log)
70 |       mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tBLM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
71 |       const linesCopy = lines.slice()
72 |       linesCopy[line - 1] = mutantLineContent
73 |       const contentToWrite = linesCopy.join('\n')
74 |       queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
75 |     }
76 |   })
77 | }
78 | 
79 |
80 |
81 | 82 | 83 | 84 | 85 |
86 | 87 |
88 | 89 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/mutators_conditionalsBoundaryMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/conditionalsBoundaryMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/conditionalsBoundaryMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:conditionalsBoundaryMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | const operators = [
 49 |   ['<', '<='],
 50 |   ['<=', '<'],
 51 |   ['>', '>='],
 52 |   ['>=', '>']
 53 | ]
 54 | 
 55 | /**
 56 |  * @description The conditionals boundary mutator replaces the relational operators `<, <=, >, >=` with their boundary counterpart.
 57 |  * @function conditionalsBoundaryMutator
 58 |  * @memberOf module:Mutators
 59 |  */
 60 | module.exports = async function conditionalsBoundaryMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 61 |   debug('Running conditionals boundary mutator on %s', filePath)
 62 | 
 63 |   walk.simple(ast, {
 64 |     BinaryExpression (node) {
 65 |       for (const pair of operators) {
 66 |         if (node.operator !== pair[0]) {
 67 |           continue
 68 |         }
 69 |         const line = node.loc.start.line
 70 |         const lineContent = lines[line - 1]
 71 | 
 72 |         const mutantLineContent = lineContent.substr(0, node.loc.start.column) +
 73 |           lineContent.substr(node.loc.start.column, node.loc.end.column - node.loc.start.column).replace(pair[0], pair[1]) +
 74 |           lineContent.substr(node.loc.end.column)
 75 | 
 76 |         const mutantId = ++mutodeInstance.mutants
 77 |         const diff = lineDiff(lineContent, mutantLineContent)
 78 |         const log = `MUTANT ${mutantId}:\tCBM Line ${line}:\t${diff}`
 79 |         debug(log)
 80 |         mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tCBM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 81 |         const linesCopy = lines.slice()
 82 |         linesCopy[line - 1] = mutantLineContent
 83 |         const contentToWrite = linesCopy.join('\n')
 84 |         queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 85 |       }
 86 |     }
 87 |   })
 88 | }
 89 | 
90 |
91 |
92 | 93 | 94 | 95 | 96 |
97 | 98 |
99 | 100 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /docs/mutators_deletionMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/deletionMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/deletionMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const chalk = require('chalk')
 44 | const debug = require('debug')('mutode:deletionMutator')
 45 | 
 46 | const mutantRunner = require('../mutantRunner')
 47 | 
 48 | /**
 49 |  * @description Mutator that traverses files and deletes single line statements.
 50 |  * @function deletionMutator
 51 |  * @memberOf module:Mutators
 52 |  */
 53 | module.exports = async function deletionMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 54 |   debug('Running deletion mutator on %s', filePath)
 55 | 
 56 |   const linesCheck = {}
 57 | 
 58 |   walk.simple(ast, {
 59 |     Statement (node) {
 60 |       if (linesCheck[node.loc.start.line] || node.type === 'BlockStatement' || (node.consequent && node.consequent.type === 'BlockStatement')) {
 61 |         debug('Skipped line', node.loc.start.line)
 62 |         return
 63 |       }
 64 |       const line = node.loc.start.line
 65 |       const lineContent = lines[line - 1]
 66 | 
 67 |       linesCheck[line] = true
 68 | 
 69 |       if (lineContent.trim().startsWith('console.') || lineContent.trim().startsWith('debug(')) {
 70 |         debug('Logging line, continuing')
 71 |         return
 72 |       }
 73 |       if (lineContent.trim().endsWith('{') || lineContent.trim().startsWith('}')) {
 74 |         debug('Code block line, continuing')
 75 |         return
 76 |       }
 77 | 
 78 |       const mutantId = ++mutodeInstance.mutants
 79 |       const log = `MUTANT ${mutantId}:\tDM Deleted line ${line}:\t${chalk.inverse(lineContent.trim())}`
 80 |       debug(log)
 81 |       mutodeInstance.mutantLog(log)
 82 |       const contentToWrite = lines.slice(0, line - 1).concat(lines.slice(line, lines.length)).join('\n')
 83 |       queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 84 |     }
 85 |   })
 86 | }
 87 | 
88 |
89 |
90 | 91 | 92 | 93 | 94 |
95 | 96 |
97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/mutators_incrementsMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/incrementsMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/incrementsMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:incrementsMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | const operators = [
 49 |   ['++', '--'],
 50 |   ['--', '++']
 51 | ]
 52 | 
 53 | /**
 54 |  * @description Mutates increments (`i++`) / decrements (`i--`) statements to their counterparts.
 55 |  * @function incrementsMutator
 56 |  * @memberOf module:Mutators
 57 |  */
 58 | module.exports = async function incrementsMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 59 |   debug('Running increments mutator on %s', filePath)
 60 | 
 61 |   walk.simple(ast, {
 62 |     UpdateExpression (node) {
 63 |       for (const pair of operators) {
 64 |         if (node.operator !== pair[0]) {
 65 |           continue
 66 |         }
 67 |         const line = node.loc.start.line
 68 |         const lineContent = lines[line - 1]
 69 | 
 70 |         const mutantLineContent = lineContent.substr(0, node.loc.start.column) +
 71 |           lineContent.substr(node.loc.start.column, node.loc.end.column - node.loc.start.column).replace(pair[0], pair[1]) +
 72 |           lineContent.substr(node.loc.end.column)
 73 | 
 74 |         const mutantId = ++mutodeInstance.mutants
 75 |         const diff = lineDiff(lineContent, mutantLineContent)
 76 |         const log = `MUTANT ${mutantId}:\tIM Line ${line}:\t${diff}...`
 77 |         debug(log)
 78 |         mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tIM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 79 |         const linesCopy = lines.slice()
 80 |         linesCopy[line - 1] = mutantLineContent
 81 |         const contentToWrite = linesCopy.join('\n')
 82 |         queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 83 |       }
 84 |     }
 85 |   })
 86 | }
 87 | 
88 |
89 |
90 | 91 | 92 | 93 | 94 |
95 | 96 |
97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/mutators_invertNegativesMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/invertNegativesMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/invertNegativesMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
43 | const debug = require('debug')('mutode:invertNegativesMutator')
44 | 
45 | const mutantRunner = require('../mutantRunner')
46 | const lineDiff = require('../util/lineDiff')
47 | 
48 | /**
49 |  * @description Mutates `-a` to `a`.
50 |  * @function invertNegativesMutator
51 |  * @memberOf module:Mutators
52 |  */
53 | module.exports = async function invertNegativesMutator ({mutodeInstance, filePath, lines, queue, ast}) {
54 |   debug('Running invert negatives mutator on %s', filePath)
55 | 
56 |   walk.simple(ast, {
57 |     UnaryExpression (node) {
58 |       if (node.operator !== '-') {
59 |         return
60 |       }
61 |       const line = node.loc.start.line
62 |       const lineContent = lines[line - 1]
63 | 
64 |       const mutantLineContent = lineContent.substr(0, node.loc.start.column) +
65 |         lineContent.substr(node.loc.start.column + 1)
66 | 
67 |       const mutantId = ++mutodeInstance.mutants
68 |       const diff = lineDiff(lineContent, mutantLineContent)
69 |       const log = `MUTANT ${mutantId}:\tINM Line ${line}:\t${diff}...`
70 |       debug(log)
71 |       mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tINM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
72 |       const linesCopy = lines.slice()
73 |       linesCopy[line - 1] = mutantLineContent
74 |       const contentToWrite = linesCopy.join('\n')
75 |       queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
76 |     }
77 |   })
78 | }
79 | 
80 |
81 |
82 | 83 | 84 | 85 | 86 |
87 | 88 |
89 | 90 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /docs/mutators_mathMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/mathMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/mathMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:mathMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | const operators = [
 49 |   ['+', '-'],
 50 |   ['-', '+'],
 51 |   ['*', '/'],
 52 |   ['/', '*'],
 53 |   ['%', '*'],
 54 |   ['&', '|'],
 55 |   ['|', '&'],
 56 |   ['^', '|'],
 57 |   ['<<', '>>'],
 58 |   ['>>', '<<'],
 59 |   ['**', '*']
 60 | ]
 61 | 
 62 | /**
 63 |  * @description Mutates math and bitwise operators to their inverse. The modulus operator `%` and the exponential operator `**` are mutated to multiplication `*`.
 64 |  * @function mathMutator
 65 |  * @memberOf module:Mutators
 66 |  */
 67 | module.exports = async function mathMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 68 |   debug('Running math mutator on %s', filePath)
 69 |   walk.simple(ast, {
 70 |     BinaryExpression (node) {
 71 |       for (const pair of operators) {
 72 |         if (node.operator !== pair[0] || node.left.loc.end - node.right.loc.start > 5) {
 73 |           continue
 74 |         }
 75 |         const line = node.loc.start.line
 76 |         const lineContent = lines[line - 1]
 77 | 
 78 |         const mutantLineContent = lineContent.substr(0, node.left.loc.end.column) +
 79 |           lineContent.substr(node.left.loc.end.column, node.right.loc.start.column - node.left.loc.end.column).replace(pair[0], pair[1]) +
 80 |           lineContent.substr(node.right.loc.start.column)
 81 | 
 82 |         const mutantId = ++mutodeInstance.mutants
 83 |         const diff = lineDiff(lineContent, mutantLineContent)
 84 |         const log = `MUTANT ${mutantId}:\tMM Line ${line}:\t${diff}`
 85 |         debug(log)
 86 |         mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tMM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 87 |         const linesCopy = lines.slice()
 88 |         linesCopy[line - 1] = mutantLineContent
 89 |         const contentToWrite = linesCopy.join('\n')
 90 |         queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 91 |       }
 92 |     }
 93 |   })
 94 | }
 95 | 
96 |
97 |
98 | 99 | 100 | 101 | 102 |
103 | 104 |
105 | 106 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/mutators_negateConditionalsMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/negateConditionalsMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/negateConditionalsMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:negateConditionalsMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | const operators = [
 49 |   ['==', '!='],
 50 |   ['!=', '=='],
 51 |   ['===', '!=='],
 52 |   ['!==', '==='],
 53 |   ['<=', '>'],
 54 |   ['>', '<='],
 55 |   ['>=', '<'],
 56 |   ['<', '>=']
 57 | ]
 58 | 
 59 | /**
 60 |  * @description Mutates conditionals to their inverse.
 61 |  * @function negateConditionalsMutator
 62 |  * @memberOf module:Mutators
 63 |  */
 64 | module.exports = async function negateConditionalsMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 65 |   debug('Running negate conditionals mutator on %s', filePath)
 66 | 
 67 |   walk.simple(ast, {
 68 |     BinaryExpression (node) {
 69 |       for (const pair of operators) {
 70 |         if (node.operator !== pair[0]) {
 71 |           continue
 72 |         }
 73 |         const line = node.loc.start.line
 74 |         const lineContent = lines[line - 1]
 75 | 
 76 |         const mutantLineContent = lineContent.substr(0, node.loc.start.column) +
 77 |           lineContent.substr(node.loc.start.column, node.loc.end.column - node.loc.start.column).replace(pair[0], pair[1]) +
 78 |           lineContent.substr(node.loc.end.column)
 79 | 
 80 |         const mutantId = ++mutodeInstance.mutants
 81 |         const diff = lineDiff(lineContent, mutantLineContent)
 82 |         const log = `MUTANT ${mutantId}:\tNCM Line ${line}:\t${diff}`
 83 |         debug(log)
 84 |         mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tNCM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 85 |         const linesCopy = lines.slice()
 86 |         linesCopy[line - 1] = mutantLineContent
 87 |         const contentToWrite = linesCopy.join('\n')
 88 |         queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 89 |       }
 90 |     }
 91 |   })
 92 | }
 93 | 
94 |
95 |
96 | 97 | 98 | 99 | 100 |
101 | 102 |
103 | 104 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /docs/mutators_numericLiteralsMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/numericLiteralsMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/numericLiteralsMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:numericLiteralsMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | /**
 49 |  * @description Mutates numeric literals values.
 50 |  * Numeric literals are mutated to *value + 1*, *value - 1*, *random value*, and 0 if not previously 0.
 51 |  * @function numericLiteralsMutator
 52 |  * @memberOf module:Mutators
 53 |  */
 54 | module.exports = async function numericLiteralsMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 55 |   debug('Running numeric literals mutator on %s', filePath)
 56 | 
 57 |   walk.simple(ast, {
 58 |     NumericLiteral (node) {
 59 |       const line = node.loc.start.line
 60 |       const lineContent = lines[line - 1]
 61 | 
 62 |       const newValues = []
 63 | 
 64 |       if (node.value !== 0) newValues.push(0)
 65 |       if (node.value !== 1) newValues.push(node.value - 1)
 66 |       newValues.push(node.value + 1)
 67 |       newValues.push(Math.floor(Math.random() * 1000000))
 68 | 
 69 |       for (const newValue of newValues) {
 70 |         const mutantLineContent = lineContent.substr(0, node.loc.start.column) +
 71 |           newValue +
 72 |           lineContent.substr(node.loc.end.column)
 73 | 
 74 |         const mutantId = ++mutodeInstance.mutants
 75 |         const diff = lineDiff(lineContent, mutantLineContent)
 76 |         const log = `MUTANT ${mutantId}:\tNLM Line ${line}:\t${diff}...`
 77 |         debug(log)
 78 |         mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tNLM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 79 |         const linesCopy = lines.slice()
 80 |         linesCopy[line - 1] = mutantLineContent
 81 |         const contentToWrite = linesCopy.join('\n')
 82 |         queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 83 |       }
 84 |     }
 85 |   })
 86 | }
 87 | 
88 |
89 |
90 | 91 | 92 | 93 | 94 |
95 | 96 |
97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/mutators_removeConditionalsMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/removeConditionalsMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/removeConditionalsMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:removeConditionalsMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | const operators = [
 49 |   '==',
 50 |   '!=',
 51 |   '===',
 52 |   '!=='
 53 | ]
 54 | 
 55 | /**
 56 |  * @description Mutates equality conditionals (`==, ===, !=, !==`) to both `true` and `false` literals
 57 |  * @function removeConditionalsMutator
 58 |  * @memberOf module:Mutators
 59 |  */
 60 | module.exports = async function removeConditionalsMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 61 |   debug('Running remove conditionals mutator on %s', filePath)
 62 | 
 63 |   walk.simple(ast, {
 64 |     BinaryExpression (node) {
 65 |       for (const operator of operators) {
 66 |         if (node.operator !== operator) {
 67 |           continue
 68 |         }
 69 |         const line = node.loc.start.line
 70 |         const lineContent = lines[line - 1]
 71 | 
 72 |         for (const replacement of ['true', 'false']) {
 73 |           const mutantLineContent = lineContent.substr(0, node.loc.start.column) +
 74 |             replacement +
 75 |             lineContent.substr(node.loc.end.column)
 76 | 
 77 |           const mutantId = ++mutodeInstance.mutants
 78 |           const diff = lineDiff(lineContent, mutantLineContent)
 79 |           const log = `MUTANT ${mutantId}:\tRCM Line ${line}:\t${diff}`
 80 |           debug(log)
 81 |           mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tRCM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 82 |           const linesCopy = lines.slice()
 83 |           linesCopy[line - 1] = mutantLineContent
 84 |           const contentToWrite = linesCopy.join('\n')
 85 |           queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 86 |         }
 87 |       }
 88 |     }
 89 |   })
 90 | }
 91 | 
92 |
93 |
94 | 95 | 96 | 97 | 98 |
99 | 100 |
101 | 102 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/mutators_removeFuncParamsMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/removeFuncParamsMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/removeFuncParamsMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:removeFuncDeclarationParamsMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | /**
 49 |  * @description Mutates function declarations removing single parameters
 50 |  * @function removeFuncDeclarationParamsMutator
 51 |  * @memberOf module:Mutators
 52 |  */
 53 | module.exports = async function removeFuncDeclarationParamsMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 54 |   debug('Running remove function declaration parameters mutator on %s', filePath)
 55 | 
 56 |   walk.simple(ast, {
 57 |     FunctionDeclaration (functionNode) {
 58 |       for (const node of functionNode.params) {
 59 |         const line = node.loc.start.line
 60 |         const lineContent = lines[line - 1]
 61 | 
 62 |         let trimmed = false
 63 |         let start = lineContent.substr(0, node.loc.start.column)
 64 |         if (start.trim().endsWith(',')) {
 65 |           start = start.substr(0, start.lastIndexOf(','))
 66 |           trimmed = true
 67 |         }
 68 |         let end = lineContent.substr(node.loc.end.column)
 69 |         if (!trimmed && end.startsWith(',')) end = end.substr(1).trim()
 70 |         const mutantLineContent = start + end
 71 | 
 72 |         const mutantId = ++mutodeInstance.mutants
 73 |         const diff = lineDiff(lineContent, mutantLineContent)
 74 |         const log = `MUTANT ${mutantId}:\tRFDPM Line ${line}:\t${diff}`
 75 |         debug(log)
 76 |         mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tRFDPM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 77 |         const linesCopy = lines.slice()
 78 |         linesCopy[line - 1] = mutantLineContent
 79 |         const contentToWrite = linesCopy.join('\n')
 80 |         queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 81 |       }
 82 |     }
 83 |   })
 84 | }
 85 | 
86 |
87 |
88 | 89 | 90 | 91 | 92 |
93 | 94 |
95 | 96 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/mutators_removeFunctionsMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/removeFunctionsMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/removeFunctionsMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
43 | const debug = require('debug')('mutode:removeFunctionsMutator')
44 | 
45 | const mutantRunner = require('../mutantRunner')
46 | 
47 | /**
48 |  * @description Mutates functions by commenting them
49 |  * @function removeFunctionsMutator
50 |  * @memberOf module:Mutators
51 |  */
52 | module.exports = async function removeFunctionsMutator ({mutodeInstance, filePath, lines, queue, ast}) {
53 |   debug('Running remove functions mutator on %s', filePath)
54 | 
55 |   walk.simple(ast, {
56 |     Function (node) {
57 |       const line = node.loc.start.line
58 |       const functionName = node.id ? node.id.name : node.key ? node.key.name : '(anonymous / assigned)'
59 | 
60 |       const mutantId = ++mutodeInstance.mutants
61 |       const log = `MUTANT ${mutantId}:\tRFM Lines ${node.loc.start.line}-${node.loc.end.line}: Commented function ${functionName}`
62 |       debug(log)
63 |       mutodeInstance.mutantLog(log)
64 |       const linesCopy = lines.slice()
65 |       for (let i = line - 1; i < node.loc.end.line; i++) {
66 |         linesCopy[i] = `// ${linesCopy[i]}`
67 |       }
68 |       const contentToWrite = linesCopy.join('\n')
69 |       queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
70 |     }
71 |   })
72 | }
73 | 
74 |
75 |
76 | 77 | 78 | 79 | 80 |
81 | 82 |
83 | 84 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/mutators_removeLinesMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/removeLinesMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/removeLinesMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const chalk = require('chalk')
 44 | const debug = require('debug')('mutode:removeLinesMutator')
 45 | 
 46 | const mutantRunner = require('../mutantRunner')
 47 | 
 48 | /**
 49 |  * @description Mutator that comments single line statements
 50 |  * @function removeLinesMutator
 51 |  * @memberOf module:Mutators
 52 |  */
 53 | module.exports = async function removeLinesMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 54 |   debug('Running remove lines mutator on %s', filePath)
 55 | 
 56 |   const linesCheck = {}
 57 | 
 58 |   walk.simple(ast, {
 59 |     Statement (node) {
 60 |       if (node.loc.start.line !== node.loc.end.line) {
 61 |         debug('Multi line statement, continuing')
 62 |         return
 63 |       }
 64 |       if (linesCheck[node.loc.start.line]) {
 65 |         debug('Already checked line, continuing')
 66 |         return
 67 |       }
 68 |       const line = node.loc.start.line
 69 |       const lineContent = lines[line - 1]
 70 | 
 71 |       linesCheck[line] = true
 72 | 
 73 |       if (lineContent.trim().startsWith('console.') || lineContent.trim().startsWith('debug(')) {
 74 |         debug('Logging line, continuing')
 75 |         return
 76 |       }
 77 |       if (/^module.exports.?=/.test(lineContent) || /^exports.?=/.test(lineContent)) {
 78 |         debug('Exports line, continuing')
 79 |         return
 80 |       }
 81 |       if (lineContent.trim().endsWith('{')) {
 82 |         debug('Code block line, continuing')
 83 |         return
 84 |       }
 85 | 
 86 |       const mutantId = ++mutodeInstance.mutants
 87 |       const log = `MUTANT ${mutantId}:\tRLM Commented line ${line}:\t${chalk.inverse(lineContent.trim())}`
 88 |       debug(log)
 89 |       mutodeInstance.mutantLog(log)
 90 |       const linesCopy = lines.slice()
 91 |       linesCopy[line - 1] = `// ${lineContent}`
 92 |       const contentToWrite = linesCopy.join('\n')
 93 |       queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 94 |     }
 95 |   })
 96 | }
 97 | 
98 |
99 |
100 | 101 | 102 | 103 | 104 |
105 | 106 |
107 | 108 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/mutators_removeSwitchCasesMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/removeSwitchCasesMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/removeSwitchCasesMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
43 | const debug = require('debug')('mutode:removeSwitchCasesMutator')
44 | 
45 | const mutantRunner = require('../mutantRunner')
46 | 
47 | /**
48 |  * @description Mutates switch statement by removing single cases
49 |  * @function removeSwitchCasesMutator
50 |  * @memberOf module:Mutators
51 |  */
52 | module.exports = async function removeSwitchCasesMutator ({mutodeInstance, filePath, lines, queue, ast}) {
53 |   debug('Running remove switch cases mutator on %s', filePath)
54 | 
55 |   walk.simple(ast, {
56 |     SwitchCase (node) {
57 |       const line = node.loc.start.line
58 |       const caseContent = node.test ? node.test.extra ? node.test.extra.raw : `${node.test.value}` : 'default'
59 | 
60 |       const mutantId = ++mutodeInstance.mutants
61 |       const log = `MUTANT ${mutantId}:\tRSCM Lines ${node.loc.start.line}-${node.loc.end.line}: Commented case ${caseContent}`
62 |       debug(log)
63 |       mutodeInstance.mutantLog(log)
64 |       const linesCopy = lines.slice()
65 |       for (let i = line - 1; i < node.loc.end.line; i++) {
66 |         linesCopy[i] = `// ${linesCopy[i]}`
67 |       }
68 |       const contentToWrite = linesCopy.join('\n')
69 |       queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
70 |     }
71 |   })
72 | }
73 | 
74 |
75 |
76 | 77 | 78 | 79 | 80 |
81 | 82 |
83 | 84 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/mutators_returnValuesMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/returnValuesMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/returnValuesMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:incrementsMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | /**
 49 |  * @description Mutates return values.
 50 |  * Negates booleans.
 51 |  * Numbers > 0 are mutated to 0, 0 is mutated to 1.
 52 |  * String are mutated to an empty string. Empty string are mutated to a random string.
 53 |  * @function returnValuesMutator
 54 |  * @memberOf module:Mutators
 55 |  */
 56 | module.exports = async function returnValuesMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 57 |   debug('Running return values mutator on %s', filePath)
 58 | 
 59 |   walk.simple(ast, {
 60 |     ReturnStatement (node) {
 61 |       const line = node.loc.start.line
 62 |       const lineContent = lines[line - 1]
 63 |       let mutantLineContent = lineContent
 64 |       switch (node.argument.type) {
 65 |         case 'BooleanLiteral': {
 66 |           const newReturnValue = !node.argument.value
 67 |           mutantLineContent = lineContent.substr(0, node.argument.loc.start.column) +
 68 |             newReturnValue +
 69 |             lineContent.substr(node.argument.loc.end.column)
 70 |           break
 71 |         }
 72 |         case 'NumericLiteral': {
 73 |           const newReturnValue = node.argument.value === 0 ? 1 : 0
 74 |           mutantLineContent = lineContent.substr(0, node.argument.loc.start.column) +
 75 |             newReturnValue +
 76 |             lineContent.substr(node.argument.loc.end.column)
 77 |           break
 78 |         }
 79 |         case 'StringLiteral': {
 80 |           const newReturnValue = node.argument.value.length === 0 ? `'${Math.random().toString(36).replace(/[^a-z]+/g, '')}'` : node.argument.extra.raw.replace(node.argument.value, '')
 81 |           mutantLineContent = lineContent.substr(0, node.argument.loc.start.column) +
 82 |             newReturnValue +
 83 |             lineContent.substr(node.argument.loc.end.column)
 84 |           break
 85 |         }
 86 |         default:
 87 |           return
 88 |       }
 89 | 
 90 |       const mutantId = ++mutodeInstance.mutants
 91 |       const diff = lineDiff(lineContent, mutantLineContent)
 92 |       const log = `MUTANT ${mutantId}:\tRVM Line ${line}:\t${diff}...`
 93 |       debug(log)
 94 |       mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tRVM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 95 |       const linesCopy = lines.slice()
 96 |       linesCopy[line - 1] = mutantLineContent
 97 |       const contentToWrite = linesCopy.join('\n')
 98 |       queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 99 |     }
100 |   })
101 | }
102 | 
103 |
104 |
105 | 106 | 107 | 108 | 109 |
110 | 111 |
112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/mutators_switchCasesMutator.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mutators/switchCasesMutator.js - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

mutators/switchCasesMutator.js

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
const walk = require('babylon-walk')
 43 | const debug = require('debug')('mutode:incrementsMutator')
 44 | 
 45 | const mutantRunner = require('../mutantRunner')
 46 | const lineDiff = require('../util/lineDiff')
 47 | 
 48 | /**
 49 |  * @description Mutates switch cases test values.
 50 |  * Negates booleans.
 51 |  * Numbers > 0 are mutated to 0, 0 is mutated to 1.
 52 |  * String are mutated to a random string.
 53 |  * @function switchCasesMutator
 54 |  * @memberOf module:Mutators
 55 |  */
 56 | module.exports = async function switchCasesMutator ({mutodeInstance, filePath, lines, queue, ast}) {
 57 |   debug('Running switch cases mutator on %s', filePath)
 58 | 
 59 |   walk.simple(ast, {
 60 |     SwitchCase (node) {
 61 |       const line = node.loc.start.line
 62 |       const lineContent = lines[line - 1]
 63 |       let mutantLineContent = lineContent
 64 |       if (!node.test) return
 65 |       switch (node.test.type) {
 66 |         case 'BooleanLiteral': {
 67 |           const newCaseValue = !node.test.value
 68 |           mutantLineContent = lineContent.substr(0, node.test.loc.start.column) +
 69 |             newCaseValue +
 70 |             lineContent.substr(node.test.loc.end.column)
 71 |           break
 72 |         }
 73 |         case 'NumericLiteral': {
 74 |           const newCaseValue = node.test.value === 0 ? 1 : 0
 75 |           mutantLineContent = lineContent.substr(0, node.test.loc.start.column) +
 76 |             newCaseValue +
 77 |             lineContent.substr(node.test.loc.end.column)
 78 |           break
 79 |         }
 80 |         case 'StringLiteral': {
 81 |           const newCaseValue = `'${Math.random().toString(36).replace(/[^a-z]+/g, '')}'`
 82 |           mutantLineContent = lineContent.substr(0, node.test.loc.start.column) +
 83 |             newCaseValue +
 84 |             lineContent.substr(node.test.loc.end.column)
 85 |           break
 86 |         }
 87 |       }
 88 | 
 89 |       const mutantId = ++mutodeInstance.mutants
 90 |       const diff = lineDiff(lineContent, mutantLineContent)
 91 |       const log = `MUTANT ${mutantId}:\tSCM Line ${line}:\t${diff}...`
 92 |       debug(log)
 93 |       mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tSCM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``)
 94 |       const linesCopy = lines.slice()
 95 |       linesCopy[line - 1] = mutantLineContent
 96 |       const contentToWrite = linesCopy.join('\n')
 97 |       queue.push(mutantRunner({mutodeInstance, filePath, contentToWrite, log}))
 98 |     }
 99 |   })
100 | }
101 | 
102 |
103 |
104 | 105 | 106 | 107 | 108 |
109 | 110 |
111 | 112 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /docs/namespace_Mutators.module_ConditionalsBoundaryMutator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ConditionalsBoundaryMutator - Documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 |
31 | 32 |

ConditionalsBoundaryMutator

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 |
48 | 49 | 50 |

The conditionals boundary mutator replaces the relational operators <, <=, >, >= with their boundary counterpart as per the table below.

51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
OriginalMutant
<>=
65 | 66 | 67 | 68 | 69 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
Source:
98 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
109 | 110 | 111 | 112 | 113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 |
133 | 134 |
135 | 136 | 137 | 138 | 139 |
140 | 141 |
142 | 143 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/scripts/script.js: -------------------------------------------------------------------------------- 1 | /*global document, prettyPrint */ 2 | 3 | (function() { 4 | 5 | var source, 6 | i = 0, 7 | lineNumber = 0, 8 | lineId, 9 | lines, 10 | totalLines, 11 | anchorHash, 12 | navTrigger = document.querySelector("#nav-trigger"); 13 | 14 | prettyPrint(); 15 | 16 | source = document.getElementsByClassName("prettyprint source linenums"); 17 | 18 | if (source && source[0]) { 19 | anchorHash = document.location.hash.substring(1); 20 | lines = source[0].getElementsByTagName("li"); 21 | totalLines = lines.length; 22 | 23 | for (; i < totalLines; i++) { 24 | lineNumber++; 25 | lineId = "line" + lineNumber; 26 | lines[i].id = lineId; 27 | 28 | if (lineId === anchorHash) { 29 | lines[i].className += " selected"; 30 | } 31 | } 32 | } 33 | 34 | //Closes mobile nav on item click 35 | document.querySelectorAll("nav ul > li > a").forEach(function (el) { 36 | el.onclick = function (evt) { 37 | if (navTrigger.checked) { 38 | navTrigger.checked = false; 39 | } 40 | }; 41 | }); 42 | 43 | })(); 44 | -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: hsl(240, 100%, 50%); } 17 | 18 | /* a comment */ 19 | .com { 20 | color: hsl(0, 0%, 60%); } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: hsl(240, 100%, 32%); } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: hsl(240, 100%, 40%); } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #000000; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #000000; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #000000; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /example-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "node test" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /example-module/src/discarded.js: -------------------------------------------------------------------------------- 1 | const to = setTimeout(() => {}, 20000) 2 | to.unref() 3 | -------------------------------------------------------------------------------- /example-module/src/killed-dep.js: -------------------------------------------------------------------------------- 1 | module.exports = 'hello' 2 | -------------------------------------------------------------------------------- /example-module/src/killed.js: -------------------------------------------------------------------------------- 1 | const string = require('./killed-dep') 2 | 3 | module.exports = { 4 | deletion () { 5 | return true 6 | }, 7 | math (n, m) { 8 | const a = n + m 9 | const b = n - m 10 | const c = n * m 11 | const d = n / m 12 | const e = n % m 13 | const f = n | m 14 | const g = n & m 15 | const h = n ^ m 16 | const i = n ** m 17 | const j = n << m 18 | const k = n >> m 19 | return +(a + b + c + d + e + f + g + h + i + j + k).toFixed(1) 20 | }, 21 | increments (a) { 22 | a++ 23 | a-- 24 | return a 25 | }, 26 | conditionals (a) { 27 | if (a === -1) { 28 | return 0 29 | } 30 | if (a < 2) { 31 | return a 32 | } 33 | if (a <= 3) { 34 | return a * 2 35 | } 36 | if (a >= 7) { 37 | return a * 3 38 | } 39 | if (a > 5) { 40 | return a * 4 41 | } 42 | if (a !== 4) { 43 | return -1 44 | } 45 | return a * 5 46 | }, 47 | negatives (a) { 48 | return -a 49 | }, 50 | stringLiterals: { 51 | hello () { 52 | return string 53 | }, 54 | empty () { 55 | return '' 56 | } 57 | }, 58 | numericLiterals: { 59 | zero () { 60 | return 0 61 | }, 62 | one () { 63 | return 1 64 | }, 65 | ten () { 66 | return 10 67 | } 68 | }, 69 | booleanLiterals: { 70 | booleanTrue () { 71 | return true 72 | }, 73 | booleanFalse () { 74 | return false 75 | } 76 | }, 77 | functions () { 78 | const b = function () { 79 | return 2 80 | } 81 | 82 | function a (p1, p2, p3) { 83 | return p1 * p2 * p3 84 | } 85 | 86 | return a(1, 2, 3) * b() 87 | }, 88 | arrays () { 89 | const a = [1, 2, 3] 90 | const b = [ 91 | 4, 92 | 5, 93 | { 94 | a: 6, 95 | b: 7 96 | } 97 | ] 98 | return a.concat(b) 99 | }, 100 | objects (bool) { 101 | const a = { a: 1, b: 2 } 102 | const b = { 103 | a: 3, 104 | b: [ 105 | 4, 5 106 | ] 107 | } 108 | return bool ? a : b 109 | }, 110 | switchCases (a) { 111 | switch (a) { 112 | case 1: 113 | return 2 114 | case true: 115 | return 3 116 | default: 117 | return 4 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /example-module/src/no-ast.js: -------------------------------------------------------------------------------- 1 | π is fund -------------------------------------------------------------------------------- /example-module/src/survived.js: -------------------------------------------------------------------------------- 1 | function other (a = 0) { 2 | for (let i = 0; false;) { 3 | } 4 | console.log('') 5 | a++ 6 | a-- 7 | } 8 | 9 | module.exports = undefined 10 | -------------------------------------------------------------------------------- /example-module/test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const killed = require('./src/killed') 3 | const survived = require('./src/survived') 4 | const discarded = require('./src/discarded') 5 | 6 | // Deletion 7 | assert.ok(killed.deletion()) 8 | 9 | // Math 10 | assert.strictEqual(killed.math(1, 2), 16.5) 11 | assert.strictEqual(killed.math(2, 1), 21) 12 | assert.strictEqual(killed.math(2, 3), 42.7) 13 | assert.strictEqual(killed.math(3, 2), 41.5) 14 | assert.strictEqual(killed.math(3, 4), 164.8) 15 | assert.strictEqual(killed.math(4, 3), 132.3) 16 | 17 | // Increments 18 | assert.strictEqual(killed.increments(1), 1) 19 | assert.strictEqual(killed.increments(10), 10) 20 | 21 | // Conditionals 22 | assert.strictEqual(killed.conditionals(-1), 0) 23 | assert.strictEqual(killed.conditionals(0), 0) 24 | assert.strictEqual(killed.conditionals(1), 1) 25 | assert.strictEqual(killed.conditionals(2), 4) 26 | assert.strictEqual(killed.conditionals(3), 6) 27 | assert.strictEqual(killed.conditionals(4), 20) 28 | assert.strictEqual(killed.conditionals(5), -1) 29 | assert.strictEqual(killed.conditionals(6), 24) 30 | assert.strictEqual(killed.conditionals(7), 21) 31 | 32 | // Invert negatives 33 | assert.strictEqual(killed.negatives(-1), 1) 34 | assert.strictEqual(killed.negatives(0), -0) 35 | assert.strictEqual(killed.negatives(1), -1) 36 | 37 | // String literals 38 | assert.strictEqual(killed.stringLiterals.hello(), 'hello') 39 | assert.strictEqual(killed.stringLiterals.empty(), '') 40 | 41 | // Numeric literals 42 | assert.strictEqual(killed.numericLiterals.zero(), 0) 43 | assert.strictEqual(killed.numericLiterals.one(), 1) 44 | assert.strictEqual(killed.numericLiterals.ten(), 10) 45 | 46 | // Boolean literals 47 | assert.strictEqual(killed.booleanLiterals.booleanTrue(), true) 48 | assert.strictEqual(killed.booleanLiterals.booleanFalse(), false) 49 | 50 | // Functions 51 | assert.strictEqual(killed.functions(), 12) 52 | 53 | // Arrays 54 | assert.deepStrictEqual(killed.arrays(), [1, 2, 3, 4, 5, { a: 6, b: 7 }]) 55 | 56 | // Objects 57 | assert.deepStrictEqual(killed.objects(true), { a: 1, b: 2 }) 58 | assert.deepStrictEqual(killed.objects(false), { a: 3, b: [4, 5] }) 59 | 60 | // Switch cases 61 | assert.deepStrictEqual(killed.switchCases(1), 2) 62 | assert.deepStrictEqual(killed.switchCases(true), 3) 63 | assert.deepStrictEqual(killed.switchCases('hello'), 4) 64 | 65 | // Discarded 66 | assert.deepStrictEqual(discarded, {}) 67 | 68 | // Survived 69 | assert.strictEqual(survived, undefined) 70 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "default": { 4 | "packages": [ 5 | "package.json" 6 | ] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /no-tests-module/index.js: -------------------------------------------------------------------------------- 1 | console.log('I have no tests') 2 | -------------------------------------------------------------------------------- /no-tests-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "exit 1" 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mutode", 3 | "version": "1.4.2", 4 | "description": "Mutation testing for Node.js and JavaScript", 5 | "main": "src/mutode.js", 6 | "engines": { 7 | "node": ">=8.0.0" 8 | }, 9 | "bin": { 10 | "mutode": "./bin/mutode" 11 | }, 12 | "scripts": { 13 | "jsdoc": "jsdoc -c .jsdoc.json", 14 | "test": "standard src && nyc ava test/* && nyc report --reporter=text-lcov | coveralls" 15 | }, 16 | "keywords": [ 17 | "mutation", 18 | "testing", 19 | "mutant", 20 | "mutants", 21 | "test", 22 | "tests", 23 | "framework", 24 | "tool" 25 | ], 26 | "author": "Diego Rodríguez Baquero (https://diegorbaquero.com)", 27 | "license": "MIT", 28 | "dependencies": { 29 | "async": "3.1.0", 30 | "babylon": "^6.18.0", 31 | "babylon-walk": "^1.0.2", 32 | "chalk": "3.0.0", 33 | "debug": "4.1.1", 34 | "del": "5.1.0", 35 | "diff": "4.0.2", 36 | "escape-string-regexp": "2.0.0", 37 | "globby": "10.0.1", 38 | "mkdirp": "^0.5.1", 39 | "pretty-ms": "5.1.0", 40 | "recursive-copy": "2.0.10", 41 | "strip-ansi": "6.0.0", 42 | "terminate": "2.1.2", 43 | "yargs": "15.0.2" 44 | }, 45 | "devDependencies": { 46 | "ava": "0.25.0", 47 | "coveralls": "3.0.9", 48 | "minami": "^1.2.3", 49 | "nyc": "14.1.1", 50 | "standard": "14.3.1" 51 | }, 52 | "nyc": { 53 | "include": [ 54 | "src" 55 | ] 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "git+ssh://git@github.com/TheSoftwareDesignLab/mutode.git" 60 | }, 61 | "bugs": { 62 | "url": "https://github.com/TheSoftwareDesignLab/mutode/issues" 63 | }, 64 | "homepage": "https://github.com/TheSoftwareDesignLab/mutode" 65 | } 66 | -------------------------------------------------------------------------------- /src/mutantRunner.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const spawn = require('child_process').spawn 3 | const Debug = require('debug') 4 | const fs = require('fs') 5 | const path = require('path') 6 | const terminate = require('terminate') 7 | 8 | /** 9 | * @module MutantRunner 10 | * @description Runs a given mutant in a free worker, logging one of the possible results (survived, killed or discarded) and the time of execution. 11 | * 12 | * Execution is done with the `npm test` command inside the worker's directory 13 | */ 14 | module.exports = function MutantRunner ({ mutodeInstance, filePath, contentToWrite, log }) { 15 | const debug = Debug(`mutants:${filePath}`) 16 | return async index => { 17 | await new Promise(resolve => { 18 | const startTime = process.hrtime() 19 | fs.writeFileSync(`.mutode/mutode-${mutodeInstance.id}-${index}/${filePath}`, contentToWrite) 20 | const child = spawn(mutodeInstance.npmCommand, ['test'], { cwd: path.resolve(`.mutode/mutode-${mutodeInstance.id}-${index}`) }) 21 | 22 | child.stderr.on('data', data => { 23 | debug(data.toString()) 24 | }) 25 | 26 | let timedout = false 27 | const timeout = setTimeout(() => { 28 | terminate(child.pid) 29 | timedout = true 30 | }, mutodeInstance.timeout).unref() 31 | 32 | child.on('exit', (code, signal) => { 33 | const endTime = process.hrtime(startTime) 34 | const endTimeMS = (endTime[0] * 1e3 + endTime[1] / 1e6).toFixed(0) 35 | const timeDiff = chalk.gray(`${endTimeMS} ms`) 36 | clearTimeout(timeout) 37 | if (code === 0) { 38 | console.log(`${log}\t${chalk.bgRed('survived')} ${timeDiff}`) 39 | mutodeInstance.survived++ 40 | } else if (signal || timedout) { 41 | console.log(`${log}\t${chalk.bgBlue('discarded (timeout)')} ${timeDiff}`) 42 | mutodeInstance.discarded++ 43 | } else { 44 | console.log(`${log}\t${chalk.bgGreen('killed')} ${timeDiff}`) 45 | mutodeInstance.killed++ 46 | } 47 | // console.log('exit', code) 48 | resolve() 49 | }) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/mutators/booleanLiteralsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:booleanLiteralsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | /** 8 | * @description Mutates boolean literals values. 9 | * Boolean literals are mutated to their negative. 10 | * @function booleanLiteralsMutator 11 | * @memberOf module:Mutators 12 | */ 13 | module.exports = async function booleanLiteralsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 14 | debug('Running boolean literals mutator on %s', filePath) 15 | 16 | walk.simple(ast, { 17 | BooleanLiteral (node) { 18 | const line = node.loc.start.line 19 | const lineContent = lines[line - 1] 20 | 21 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 22 | !node.value + 23 | lineContent.substr(node.loc.end.column) 24 | 25 | const mutantId = ++mutodeInstance.mutants 26 | const diff = lineDiff(lineContent, mutantLineContent) 27 | const log = `MUTANT ${mutantId}:\tBLM Line ${line}:\t${diff}...` 28 | debug(log) 29 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tBLM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 30 | const linesCopy = lines.slice() 31 | linesCopy[line - 1] = mutantLineContent 32 | const contentToWrite = linesCopy.join('\n') 33 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/mutators/conditionalsBoundaryMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:conditionalsBoundaryMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | const operators = [ 8 | ['<', '<='], 9 | ['<=', '<'], 10 | ['>', '>='], 11 | ['>=', '>'] 12 | ] 13 | 14 | /** 15 | * @description The conditionals boundary mutator replaces the relational operators `<, <=, >, >=` with their boundary counterpart. 16 | * @function conditionalsBoundaryMutator 17 | * @memberOf module:Mutators 18 | */ 19 | module.exports = async function conditionalsBoundaryMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 20 | debug('Running conditionals boundary mutator on %s', filePath) 21 | 22 | walk.simple(ast, { 23 | BinaryExpression (node) { 24 | for (const pair of operators) { 25 | if (node.operator !== pair[0]) { 26 | continue 27 | } 28 | const line = node.loc.start.line 29 | const lineContent = lines[line - 1] 30 | 31 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 32 | lineContent.substr(node.loc.start.column, node.loc.end.column - node.loc.start.column).replace(pair[0], pair[1]) + 33 | lineContent.substr(node.loc.end.column) 34 | 35 | const mutantId = ++mutodeInstance.mutants 36 | const diff = lineDiff(lineContent, mutantLineContent) 37 | const log = `MUTANT ${mutantId}:\tCBM Line ${line}:\t${diff}` 38 | debug(log) 39 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tCBM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 40 | const linesCopy = lines.slice() 41 | linesCopy[line - 1] = mutantLineContent 42 | const contentToWrite = linesCopy.join('\n') 43 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 44 | } 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/mutators/incrementsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:incrementsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | const operators = [ 8 | ['++', '--'], 9 | ['--', '++'] 10 | ] 11 | 12 | /** 13 | * @description Mutates increments (`i++`) / decrements (`i--`) statements to their counterparts. 14 | * @function incrementsMutator 15 | * @memberOf module:Mutators 16 | */ 17 | module.exports = async function incrementsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 18 | debug('Running increments mutator on %s', filePath) 19 | 20 | walk.simple(ast, { 21 | UpdateExpression (node) { 22 | for (const pair of operators) { 23 | if (node.operator !== pair[0]) { 24 | continue 25 | } 26 | const line = node.loc.start.line 27 | const lineContent = lines[line - 1] 28 | 29 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 30 | lineContent.substr(node.loc.start.column, node.loc.end.column - node.loc.start.column).replace(pair[0], pair[1]) + 31 | lineContent.substr(node.loc.end.column) 32 | 33 | const mutantId = ++mutodeInstance.mutants 34 | const diff = lineDiff(lineContent, mutantLineContent) 35 | const log = `MUTANT ${mutantId}:\tIM Line ${line}:\t${diff}...` 36 | debug(log) 37 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tIM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 38 | const linesCopy = lines.slice() 39 | linesCopy[line - 1] = mutantLineContent 40 | const contentToWrite = linesCopy.join('\n') 41 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 42 | } 43 | } 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/mutators/invertNegativesMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:invertNegativesMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | /** 8 | * @description Mutates `-a` to `a`. 9 | * @function invertNegativesMutator 10 | * @memberOf module:Mutators 11 | */ 12 | module.exports = async function invertNegativesMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 13 | debug('Running invert negatives mutator on %s', filePath) 14 | 15 | walk.simple(ast, { 16 | UnaryExpression (node) { 17 | if (node.operator !== '-') { 18 | return 19 | } 20 | const line = node.loc.start.line 21 | const lineContent = lines[line - 1] 22 | 23 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 24 | lineContent.substr(node.loc.start.column + 1) 25 | 26 | const mutantId = ++mutodeInstance.mutants 27 | const diff = lineDiff(lineContent, mutantLineContent) 28 | const log = `MUTANT ${mutantId}:\tINM Line ${line}:\t${diff}...` 29 | debug(log) 30 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tINM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 31 | const linesCopy = lines.slice() 32 | linesCopy[line - 1] = mutantLineContent 33 | const contentToWrite = linesCopy.join('\n') 34 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/mutators/mathMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:mathMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | const operators = [ 8 | ['+', '-'], 9 | ['-', '+'], 10 | ['*', '/'], 11 | ['/', '*'], 12 | ['%', '*'], 13 | ['&', '|'], 14 | ['|', '&'], 15 | ['^', '|'], 16 | ['<<', '>>'], 17 | ['>>', '<<'], 18 | ['**', '*'] 19 | ] 20 | 21 | /** 22 | * @description Mutates math and bitwise operators to their inverse. The modulus operator `%` and the exponential operator `**` are mutated to multiplication `*`. 23 | * @function mathMutator 24 | * @memberOf module:Mutators 25 | */ 26 | module.exports = async function mathMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 27 | debug('Running math mutator on %s', filePath) 28 | walk.simple(ast, { 29 | BinaryExpression (node) { 30 | for (const pair of operators) { 31 | if (node.operator !== pair[0] || node.left.loc.end - node.right.loc.start > 5) { 32 | continue 33 | } 34 | const line = node.loc.start.line 35 | const lineContent = lines[line - 1] 36 | 37 | const mutantLineContent = lineContent.substr(0, node.left.loc.end.column) + 38 | lineContent.substr(node.left.loc.end.column, node.right.loc.start.column - node.left.loc.end.column).replace(pair[0], pair[1]) + 39 | lineContent.substr(node.right.loc.start.column) 40 | 41 | const mutantId = ++mutodeInstance.mutants 42 | const diff = lineDiff(lineContent, mutantLineContent) 43 | const log = `MUTANT ${mutantId}:\tMM Line ${line}:\t${diff}` 44 | debug(log) 45 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tMM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 46 | const linesCopy = lines.slice() 47 | linesCopy[line - 1] = mutantLineContent 48 | const contentToWrite = linesCopy.join('\n') 49 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 50 | } 51 | } 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/mutators/negateConditionalsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:negateConditionalsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | const operators = [ 8 | ['==', '!='], 9 | ['!=', '=='], 10 | ['===', '!=='], 11 | ['!==', '==='], 12 | ['<=', '>'], 13 | ['>', '<='], 14 | ['>=', '<'], 15 | ['<', '>='] 16 | ] 17 | 18 | /** 19 | * @description Mutates conditionals to their inverse. 20 | * @function negateConditionalsMutator 21 | * @memberOf module:Mutators 22 | */ 23 | module.exports = async function negateConditionalsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 24 | debug('Running negate conditionals mutator on %s', filePath) 25 | 26 | walk.simple(ast, { 27 | BinaryExpression (node) { 28 | for (const pair of operators) { 29 | if (node.operator !== pair[0]) { 30 | continue 31 | } 32 | const line = node.loc.start.line 33 | const lineContent = lines[line - 1] 34 | 35 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 36 | lineContent.substr(node.loc.start.column, node.loc.end.column - node.loc.start.column).replace(pair[0], pair[1]) + 37 | lineContent.substr(node.loc.end.column) 38 | 39 | const mutantId = ++mutodeInstance.mutants 40 | const diff = lineDiff(lineContent, mutantLineContent) 41 | const log = `MUTANT ${mutantId}:\tNCM Line ${line}:\t${diff}` 42 | debug(log) 43 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tNCM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 44 | const linesCopy = lines.slice() 45 | linesCopy[line - 1] = mutantLineContent 46 | const contentToWrite = linesCopy.join('\n') 47 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 48 | } 49 | } 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /src/mutators/numericLiteralsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:numericLiteralsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | /** 8 | * @description Mutates numeric literals values. 9 | * Numeric literals are mutated to *value + 1*, *value - 1*, *random value*, and 0 if not previously 0. 10 | * @function numericLiteralsMutator 11 | * @memberOf module:Mutators 12 | */ 13 | module.exports = async function numericLiteralsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 14 | debug('Running numeric literals mutator on %s', filePath) 15 | 16 | walk.simple(ast, { 17 | NumericLiteral (node) { 18 | const line = node.loc.start.line 19 | const lineContent = lines[line - 1] 20 | 21 | const newValues = [] 22 | 23 | if (node.value !== 0) newValues.push(0) 24 | if (node.value !== 1) newValues.push(node.value - 1) 25 | newValues.push(node.value + 1) 26 | newValues.push(Math.floor(Math.random() * 1000000)) 27 | 28 | for (const newValue of newValues) { 29 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 30 | newValue + 31 | lineContent.substr(node.loc.end.column) 32 | 33 | const mutantId = ++mutodeInstance.mutants 34 | const diff = lineDiff(lineContent, mutantLineContent) 35 | const log = `MUTANT ${mutantId}:\tNLM Line ${line}:\t${diff}...` 36 | debug(log) 37 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tNLM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 38 | const linesCopy = lines.slice() 39 | linesCopy[line - 1] = mutantLineContent 40 | const contentToWrite = linesCopy.join('\n') 41 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 42 | } 43 | } 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/mutators/removeArrayElementsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:removeArrayElementsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | /** 8 | * @description Mutates array by removing single elements. 9 | * @function removeArrayElementsMutator 10 | * @memberOf module:Mutators 11 | */ 12 | module.exports = async function removeArrayElementsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 13 | debug('Running remove array elements mutator on %s', filePath) 14 | 15 | walk.simple(ast, { 16 | ArrayExpression (arrayNode) { 17 | for (const node of arrayNode.elements) { 18 | let contentToWrite = '' 19 | let log = '' 20 | if (node.loc.start.line !== node.loc.end.line) { 21 | const line = node.loc.start.line 22 | 23 | const mutantId = ++mutodeInstance.mutants 24 | log = `MUTANT ${mutantId}:\tRAEM Lines ${node.loc.start.line}-${node.loc.end.line}: Commented element #${arrayNode.elements.indexOf(node) + 1}` 25 | debug(log) 26 | mutodeInstance.mutantLog(log) 27 | const linesCopy = lines.slice() 28 | for (let i = line - 1; i < node.loc.end.line; i++) { 29 | linesCopy[i] = `// ${linesCopy[i]}` 30 | } 31 | contentToWrite = linesCopy.join('\n') 32 | } else { 33 | const line = node.loc.start.line 34 | const lineContent = lines[line - 1] 35 | 36 | let trimmed = false 37 | let start = lineContent.substr(0, node.loc.start.column) 38 | if (start.trim().endsWith(',')) { 39 | start = start.substr(0, start.lastIndexOf(',')) 40 | trimmed = true 41 | } 42 | let end = lineContent.substr(node.loc.end.column) 43 | if (!trimmed && end.startsWith(',')) end = end.substr(1).trim() 44 | const mutantLineContent = start + end 45 | 46 | const mutantId = ++mutodeInstance.mutants 47 | const diff = lineDiff(lineContent, mutantLineContent) 48 | log = `MUTANT ${mutantId}:\tRAEM Line ${line}:\t${diff}` 49 | debug(log) 50 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tRAEM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 51 | const linesCopy = lines.slice() 52 | linesCopy[line - 1] = mutantLineContent 53 | contentToWrite = linesCopy.join('\n') 54 | } 55 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 56 | } 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /src/mutators/removeConditionalsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:removeConditionalsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | const operators = [ 8 | '==', 9 | '!=', 10 | '===', 11 | '!==' 12 | ] 13 | 14 | /** 15 | * @description Mutates equality conditionals (`==, ===, !=, !==`) to both `true` and `false` literals 16 | * @function removeConditionalsMutator 17 | * @memberOf module:Mutators 18 | */ 19 | module.exports = async function removeConditionalsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 20 | debug('Running remove conditionals mutator on %s', filePath) 21 | 22 | walk.simple(ast, { 23 | BinaryExpression (node) { 24 | for (const operator of operators) { 25 | if (node.operator !== operator) { 26 | continue 27 | } 28 | const line = node.loc.start.line 29 | const lineContent = lines[line - 1] 30 | 31 | for (const replacement of ['true', 'false']) { 32 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 33 | replacement + 34 | lineContent.substr(node.loc.end.column) 35 | 36 | const mutantId = ++mutodeInstance.mutants 37 | const diff = lineDiff(lineContent, mutantLineContent) 38 | const log = `MUTANT ${mutantId}:\tRCM Line ${line}:\t${diff}` 39 | debug(log) 40 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tRCM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 41 | const linesCopy = lines.slice() 42 | linesCopy[line - 1] = mutantLineContent 43 | const contentToWrite = linesCopy.join('\n') 44 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 45 | } 46 | } 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /src/mutators/removeFuncCallArgsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:removeFuncCallArgsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | /** 8 | * @description Mutates function calls removing single arguments. 9 | * @function removeFuncCallArgsMutator 10 | * @memberOf module:Mutators 11 | */ 12 | module.exports = async function removeFuncCallArgsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 13 | debug('Running remove function call arguments mutator on %s', filePath) 14 | 15 | walk.ancestor(ast, { 16 | CallExpression (functionNode, state, ancestors) { 17 | if (ancestors.length >= 2) { 18 | const ancestor = ancestors[ancestors.length - 2] 19 | if (ancestor.type && ancestor.type === 'CallExpression' && ancestor.callee) { 20 | if (ancestor.callee.type === 'MemberExpression' && ancestor.callee.object.name === 'console') return 21 | if (ancestor.callee.name) { 22 | switch (ancestor.callee.name) { 23 | case 'require': 24 | case 'debug': 25 | return 26 | default: 27 | break 28 | } 29 | } 30 | } 31 | } 32 | 33 | for (const node of functionNode.arguments) { 34 | const line = node.loc.start.line 35 | const lineContent = lines[line - 1] 36 | 37 | let trimmed = false 38 | let start = lineContent.substr(0, node.loc.start.column) 39 | if (start.trim().endsWith(',')) { 40 | start = start.substr(0, start.lastIndexOf(',')) 41 | trimmed = true 42 | } 43 | let end = lineContent.substr(node.loc.end.column) 44 | if (!trimmed && end.startsWith(',')) end = end.substr(1).trim() 45 | const mutantLineContent = start + end 46 | 47 | const mutantId = ++mutodeInstance.mutants 48 | const diff = lineDiff(lineContent, mutantLineContent) 49 | const log = `MUTANT ${mutantId}:\tRFCAM Line ${line}:\t${diff}` 50 | debug(log) 51 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tRFCAM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 52 | const linesCopy = lines.slice() 53 | linesCopy[line - 1] = mutantLineContent 54 | const contentToWrite = linesCopy.join('\n') 55 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 56 | } 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /src/mutators/removeFuncParamsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:removeFuncDeclarationParamsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | /** 8 | * @description Mutates function declarations removing single parameters 9 | * @function removeFuncDeclarationParamsMutator 10 | * @memberOf module:Mutators 11 | */ 12 | module.exports = async function removeFuncDeclarationParamsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 13 | debug('Running remove function declaration parameters mutator on %s', filePath) 14 | 15 | walk.simple(ast, { 16 | FunctionDeclaration (functionNode) { 17 | for (const node of functionNode.params) { 18 | const line = node.loc.start.line 19 | const lineContent = lines[line - 1] 20 | 21 | let trimmed = false 22 | let start = lineContent.substr(0, node.loc.start.column) 23 | if (start.trim().endsWith(',')) { 24 | start = start.substr(0, start.lastIndexOf(',')) 25 | trimmed = true 26 | } 27 | let end = lineContent.substr(node.loc.end.column) 28 | if (!trimmed && end.startsWith(',')) end = end.substr(1).trim() 29 | const mutantLineContent = start + end 30 | 31 | const mutantId = ++mutodeInstance.mutants 32 | const diff = lineDiff(lineContent, mutantLineContent) 33 | const log = `MUTANT ${mutantId}:\tRFDPM Line ${line}:\t${diff}` 34 | debug(log) 35 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tRFDPM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 36 | const linesCopy = lines.slice() 37 | linesCopy[line - 1] = mutantLineContent 38 | const contentToWrite = linesCopy.join('\n') 39 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 40 | } 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /src/mutators/removeFunctionsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:removeFunctionsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | 6 | /** 7 | * @description Mutates functions by commenting them 8 | * @function removeFunctionsMutator 9 | * @memberOf module:Mutators 10 | */ 11 | module.exports = async function removeFunctionsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 12 | debug('Running remove functions mutator on %s', filePath) 13 | 14 | walk.simple(ast, { 15 | Function (node) { 16 | const line = node.loc.start.line 17 | const functionName = node.id ? node.id.name : node.key ? node.key.name : '(anonymous / assigned)' 18 | 19 | const mutantId = ++mutodeInstance.mutants 20 | const log = `MUTANT ${mutantId}:\tRFM Lines ${node.loc.start.line}-${node.loc.end.line}: Commented function ${functionName}` 21 | debug(log) 22 | mutodeInstance.mutantLog(log) 23 | const linesCopy = lines.slice() 24 | for (let i = line - 1; i < node.loc.end.line; i++) { 25 | linesCopy[i] = `// ${linesCopy[i]}` 26 | } 27 | const contentToWrite = linesCopy.join('\n') 28 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/mutators/removeLinesMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const chalk = require('chalk') 3 | const debug = require('debug')('mutode:removeLinesMutator') 4 | 5 | const mutantRunner = require('../mutantRunner') 6 | 7 | /** 8 | * @description Mutator that comments single line statements 9 | * @function removeLinesMutator 10 | * @memberOf module:Mutators 11 | */ 12 | module.exports = async function removeLinesMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 13 | debug('Running remove lines mutator on %s', filePath) 14 | 15 | const linesCheck = {} 16 | 17 | walk.simple(ast, { 18 | Statement (node) { 19 | if (node.loc.start.line !== node.loc.end.line) { 20 | debug('Multi line statement, continuing') 21 | return 22 | } 23 | if (linesCheck[node.loc.start.line]) { 24 | debug('Already checked line, continuing') 25 | return 26 | } 27 | const line = node.loc.start.line 28 | const lineContent = lines[line - 1] 29 | 30 | linesCheck[line] = true 31 | 32 | if (lineContent.trim().startsWith('console.') || lineContent.trim().startsWith('debug(')) { 33 | debug('Logging line, continuing') 34 | return 35 | } 36 | if (/^module.exports.?=/.test(lineContent) || /^exports.?=/.test(lineContent)) { 37 | debug('Exports line, continuing') 38 | return 39 | } 40 | if (lineContent.trim().endsWith('{')) { 41 | debug('Code block line, continuing') 42 | return 43 | } 44 | 45 | const mutantId = ++mutodeInstance.mutants 46 | const log = `MUTANT ${mutantId}:\tRLM Commented line ${line}:\t${chalk.inverse(lineContent.trim())}` 47 | debug(log) 48 | mutodeInstance.mutantLog(log) 49 | const linesCopy = lines.slice() 50 | linesCopy[line - 1] = `// ${lineContent}` 51 | const contentToWrite = linesCopy.join('\n') 52 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /src/mutators/removeObjPropsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:removeObjPropsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | /** 8 | * @description Mutates objects by removing single properties 9 | * @function removeObjPropsMutator 10 | * @memberOf module:Mutators 11 | */ 12 | module.exports = async function removeObjPropsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 13 | debug('Running remove object properties mutator on %s', filePath) 14 | 15 | walk.simple(ast, { 16 | ObjectProperty (node) { 17 | const propertyName = node.key.name 18 | 19 | let contentToWrite = '' 20 | let log = '' 21 | if (node.loc.start.line !== node.loc.end.line) { 22 | const line = node.loc.start.line 23 | 24 | const mutantId = ++mutodeInstance.mutants 25 | log = `MUTANT ${mutantId}:\tROPM Lines ${node.loc.start.line}-${node.loc.end.line}: Commented property ${propertyName}` 26 | debug(log) 27 | mutodeInstance.mutantLog(log) 28 | const linesCopy = lines.slice() 29 | for (let i = line - 1; i < node.loc.end.line; i++) { 30 | linesCopy[i] = `// ${linesCopy[i]}` 31 | } 32 | contentToWrite = linesCopy.join('\n') 33 | } else { 34 | const line = node.loc.start.line 35 | const lineContent = lines[line - 1] 36 | 37 | let trimmed = false 38 | let start = lineContent.substr(0, node.loc.start.column) 39 | if (start.trim().endsWith(',')) { 40 | start = start.substr(0, start.lastIndexOf(',')) 41 | trimmed = true 42 | } 43 | let end = lineContent.substr(node.loc.end.column) 44 | if (!trimmed && end.startsWith(',')) end = end.substr(1).trim() 45 | const mutantLineContent = start + end 46 | 47 | const mutantId = ++mutodeInstance.mutants 48 | const diff = lineDiff(lineContent, mutantLineContent) 49 | log = `MUTANT ${mutantId}:\tROPM Line ${line}:\t${diff}` 50 | debug(log) 51 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tROPM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 52 | const linesCopy = lines.slice() 53 | linesCopy[line - 1] = mutantLineContent 54 | contentToWrite = linesCopy.join('\n') 55 | } 56 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /src/mutators/removeSwitchCasesMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:removeSwitchCasesMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | 6 | /** 7 | * @description Mutates switch statement by removing single cases 8 | * @function removeSwitchCasesMutator 9 | * @memberOf module:Mutators 10 | */ 11 | module.exports = async function removeSwitchCasesMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 12 | debug('Running remove switch cases mutator on %s', filePath) 13 | 14 | walk.simple(ast, { 15 | SwitchCase (node) { 16 | const line = node.loc.start.line 17 | const caseContent = node.test ? node.test.extra ? node.test.extra.raw : `${node.test.value}` : 'default' 18 | 19 | const mutantId = ++mutodeInstance.mutants 20 | const log = `MUTANT ${mutantId}:\tRSCM Lines ${node.loc.start.line}-${node.loc.end.line}: Commented case ${caseContent}` 21 | debug(log) 22 | mutodeInstance.mutantLog(log) 23 | const linesCopy = lines.slice() 24 | for (let i = line - 1; i < node.loc.end.line; i++) { 25 | linesCopy[i] = `// ${linesCopy[i]}` 26 | } 27 | const contentToWrite = linesCopy.join('\n') 28 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/mutators/stringLiteralsMutator.js: -------------------------------------------------------------------------------- 1 | const walk = require('babylon-walk') 2 | const debug = require('debug')('mutode:stringLiteralsMutator') 3 | 4 | const mutantRunner = require('../mutantRunner') 5 | const lineDiff = require('../util/lineDiff') 6 | 7 | /** 8 | * @description Mutates string literals values. 9 | * Strings are mutated to a random string, and to an empty string (if not previously empty). 10 | * @function stringLiteralsMutator 11 | * @memberOf module:Mutators 12 | */ 13 | module.exports = async function stringLiteralsMutator ({ mutodeInstance, filePath, lines, queue, ast }) { 14 | debug('Running string literals mutator on %s', filePath) 15 | 16 | walk.ancestor(ast, { 17 | StringLiteral (node, state, ancestors) { 18 | const line = node.loc.start.line 19 | const lineContent = lines[line - 1] 20 | 21 | if (ancestors.length >= 2) { 22 | const ancestor = ancestors[ancestors.length - 2] 23 | if (ancestor.type && ancestor.type === 'CallExpression' && ancestor.callee) { 24 | if (ancestor.callee.type === 'MemberExpression' && ancestor.callee.object.name === 'console') return 25 | if (ancestor.callee.name) { 26 | switch (ancestor.callee.name) { 27 | case 'require': 28 | case 'debug': 29 | return 30 | default: 31 | break 32 | } 33 | } 34 | } 35 | } 36 | 37 | if (node.value.length !== 0) { 38 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 39 | node.extra.raw.replace(node.value, '') + 40 | lineContent.substr(node.loc.end.column) 41 | 42 | const mutantId = ++mutodeInstance.mutants 43 | const diff = lineDiff(lineContent, mutantLineContent) 44 | const log = `MUTANT ${mutantId}:\tSLM Line ${line}:\t${diff}...` 45 | debug(log) 46 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tSLM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 47 | const linesCopy = lines.slice() 48 | linesCopy[line - 1] = mutantLineContent 49 | const contentToWrite = linesCopy.join('\n') 50 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 51 | } 52 | 53 | const newValue = `'${randomString(node.value.length || 10)}'` 54 | const mutantLineContent = lineContent.substr(0, node.loc.start.column) + 55 | newValue + 56 | lineContent.substr(node.loc.end.column) 57 | 58 | const mutantId = ++mutodeInstance.mutants 59 | const diff = lineDiff(lineContent, mutantLineContent) 60 | const log = `MUTANT ${mutantId}:\tSLM Line ${line}:\t${diff}...` 61 | debug(log) 62 | mutodeInstance.mutantLog(`MUTANT ${mutantId}:\tSLM ${filePath} Line ${line}:\t\`${lineContent.trim()}\` > \`${mutantLineContent.trim()}'\``) 63 | const linesCopy = lines.slice() 64 | linesCopy[line - 1] = mutantLineContent 65 | const contentToWrite = linesCopy.join('\n') 66 | queue.push(mutantRunner({ mutodeInstance, filePath, contentToWrite, log })) 67 | } 68 | }) 69 | } 70 | 71 | function randomString (length) { 72 | const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz' 73 | let randomstring = '' 74 | for (let i = 0; i < length; i++) { 75 | const ind = Math.floor(Math.random() * chars.length) 76 | randomstring += chars.charAt(ind) 77 | } 78 | return randomstring 79 | } 80 | -------------------------------------------------------------------------------- /src/util/lineDiff.js: -------------------------------------------------------------------------------- 1 | const jsDiff = require('diff') 2 | const chalk = require('chalk') 3 | 4 | module.exports = (lineContent, mutantLineContent) => { 5 | return jsDiff.diffWords(lineContent.trim(), mutantLineContent.trim()).map(stringDiff => { 6 | if (stringDiff.added) return chalk.bgGreen(stringDiff.value) 7 | else if (stringDiff.removed) return chalk.bgRed(stringDiff.value) 8 | else return chalk.inverse(stringDiff.value) 9 | }).join('') 10 | } 11 | -------------------------------------------------------------------------------- /test/exampleModule.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const del = require('del') 3 | 4 | const Mutode = require('../src/mutode') 5 | 6 | process.chdir('./example-module') 7 | del.sync('.mutode', { force: true }) 8 | 9 | const opts = {} 10 | 11 | if (process.env.MUTODE_CONCURRENCY) opts.concurrency = process.env.MUTODE_CONCURRENCY 12 | 13 | test.serial('Exmaple module - killed', async t => { 14 | const testOpts = Object.assign({ 15 | paths: ['src/killed.js', 'src/killed-dep.js'] 16 | }, opts) 17 | const mutode = new Mutode(testOpts) 18 | await mutode.run() 19 | t.is(mutode.killed + mutode.survived + mutode.discarded, mutode.mutants) 20 | t.is(mutode.mutants, mutode.killed) 21 | t.is(mutode.coverage, 100) 22 | await t.throws(mutode.run()) 23 | }) 24 | 25 | test.serial('Exmaple module - survived', async t => { 26 | const testOpts = Object.assign({ 27 | paths: 'src/survived.js' 28 | }, opts) 29 | const mutode = new Mutode(testOpts) 30 | await mutode.run() 31 | t.is(mutode.killed + mutode.survived + mutode.discarded, mutode.mutants) 32 | t.is(mutode.mutants, mutode.survived) 33 | t.is(mutode.coverage, 0) 34 | await t.throws(mutode.run()) 35 | }) 36 | 37 | test.serial('Exmaple module - discarded', async t => { 38 | const testOpts = Object.assign({ 39 | paths: 'src/discarded.js' 40 | }, opts) 41 | const mutode = new Mutode(testOpts) 42 | await mutode.run() 43 | t.is(mutode.killed + mutode.survived + mutode.discarded, mutode.mutants) 44 | t.is(mutode.discarded, 1) 45 | await t.throws(mutode.run()) 46 | }) 47 | 48 | test.serial('Exmaple module - no AST', async t => { 49 | const mutode = new Mutode({ 50 | paths: 'src/no-ast.js', 51 | concurrency: 1 52 | }) 53 | await t.throws(mutode.run()) 54 | }) 55 | 56 | test.serial('Exmaple module - no mutants', async t => { 57 | const mutode = new Mutode({ 58 | paths: 'src/discarded.js', 59 | mutators: 'invertNegatives', 60 | concurrency: 1 61 | }) 62 | await mutode.run() 63 | t.is(mutode.killed + mutode.survived + mutode.discarded, mutode.mutants) 64 | t.is(mutode.mutants, 0) 65 | await t.throws(mutode.run()) 66 | }) 67 | -------------------------------------------------------------------------------- /test/noTestsModule.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const del = require('del') 3 | 4 | const Mutode = require('../src/mutode') 5 | 6 | process.chdir('./no-tests-module') 7 | del.sync('.mutode', { force: true }) 8 | 9 | test.serial('No tests module', async t => { 10 | const mutode = new Mutode() 11 | await t.throws(mutode.run()) 12 | }) 13 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | const Mutode = require('../src/mutode') 4 | 5 | test('New instance - Correct', async t => { 6 | const mutode = new Mutode() 7 | t.is(mutode.mutants, 0) 8 | t.is(mutode.killed, 0) 9 | t.is(mutode.survived, 0) 10 | t.is(mutode.discarded, 0) 11 | t.is(mutode.coverage, 0) 12 | }) 13 | 14 | test('New instance - Empty paths', async t => { 15 | t.throws(() => { 16 | const mutodeFail = new Mutode({ paths: 'hello.js' }) 17 | mutodeFail.run() 18 | }) 19 | }) 20 | --------------------------------------------------------------------------------