├── .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 [](http://npmjs.com/package/mutode) [](http://npmjs.com/package/mutode) [](LICENSE)
2 |
3 | [](https://standardjs.com)
4 | [](https://travis-ci.org/TheSoftwareDesignLab/mutode)
5 | [](https://ci.appveyor.com/project/DiegoRBaquero/mutode/branch/master)
6 | [](https://coveralls.io/github/TheSoftwareDesignLab/mutode?branch=master)
7 | [](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 |
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 | Original |
62 | Mutant |
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 | - 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 | Original |
59 | Mutant |
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 | - 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 | Original |
59 | Mutant |
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 | - 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 |
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 | Original |
55 | Mutant |
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 | - 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 |
--------------------------------------------------------------------------------