Skip to content

Commit 1dc3fea

Browse files
committed
🎉 initial commit
0 parents  commit 1dc3fea

File tree

155 files changed

+29505
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+29505
-0
lines changed

README.md

+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
# Mutate++ - A C++ Mutation Test Environment
2+
3+
[Mutation testing](https://en.wikipedia.org/wiki/Mutation_testing) is a technique to detect bugs in a program by
4+
changing its soure code (called "mutation") and checking whether the program's test suite detects this mutation. The
5+
mutations are designed to mimick typical programming errors (e.g., off-by-one errors). If such errors are not noticed
6+
by the test suite, the "survived" mutation can be used to create an additional test that would detect it.
7+
8+
- [Overview](#overview)
9+
- [Installation](#installation)
10+
- [Example](#example)
11+
- [Further features](#further-features)
12+
- [Help!](#help)
13+
- [Used third-party tools](#used-third-party-tools)
14+
- [License](#license)
15+
16+
17+
## Overview
18+
19+
Mutate++ is a mutation test environment for C++ programs. It supports you with the following tasks:
20+
21+
- create mutations of the source code
22+
- execute the test suite for each mutation
23+
- evaluate the outcome of the tests
24+
25+
Mutate++ is a web app that runs locally on your machine. All computation is locally, and no data is shared with anyone.
26+
27+
28+
## Installation
29+
30+
You need Python 3 which can be [downloaded here](https://www.python.org/downloads/) or installed by your operating
31+
systems's package manager.
32+
33+
After checking out the sources from this repository, you need to create a
34+
[virtual environment](https://docs.python.org/3/tutorial/venv.html) and install the required packages:
35+
36+
```bash
37+
virtualenv -p python3 venv
38+
venv/bin/pip install -r requirements
39+
```
40+
41+
Next, we need to create a database where mutations and the results are stored:
42+
43+
```bash
44+
venv/bin/python3 db_create.py
45+
```
46+
47+
The database file is an SQLite3 database `app/app.db`. You can then run the app with
48+
49+
```bash
50+
venv/bin/python3 run.py
51+
```
52+
53+
and open it in your browser with the URL <http://127.0.0.1:5000>.
54+
55+
56+
## Example
57+
58+
We use a small [example project](https://github.com/bast/cmake-example) to demonstrate the required steps to set up a
59+
project in Mutate++. We assume that you have git, CMake, make, and a C++ compiler installed.
60+
61+
62+
### 1. Prepare the project
63+
64+
Mutate++ will run the following (simplified) workflow:
65+
66+
![Workflow](doc/workflow.png)
67+
68+
We now set up the project so that we can build and test the project ourselves.
69+
70+
```bash
71+
cd /tmp
72+
git clone https://github.com/bast/cmake-example.git
73+
cd cmake-example
74+
mkdir build
75+
cd build
76+
cmake ..
77+
```
78+
79+
We now have three directories we will be refering to later:
80+
81+
- the working directory `/tmp/cmake-example/build`: this is the directory where we will execute the build and test
82+
commands
83+
- the source directory `/tmp/cmake-example/src`: this is the directory with the source code
84+
85+
Let's try building and testing:
86+
87+
```bash
88+
cd /tmp/cmake-example/build
89+
make
90+
ctest
91+
```
92+
93+
We see that 100% of the tests passed.
94+
95+
In the following, we shall refer to these commands as:
96+
97+
- the build command `make`: this is the command that builds the project from source
98+
- the test command `ctest`: this is the command that executes the test suite
99+
100+
101+
### 2. Create project in Mutate++
102+
103+
![Workflow](doc/1_create_project.png)
104+
105+
- Open <http://127.0.0.1:5000> in your browser.
106+
- Click on ["Projects"](http://127.0.0.1:5000/projects).
107+
- Click on ["New Project](http://127.0.0.1:5000/projects/create).
108+
- Enter `Example project` as project name.
109+
- Enter `/tmp/cmake-example/build` as working directory.
110+
- Enter `make` as build command.
111+
- Leave "Quickcheck command" and "Quickcheck timeout" empty.
112+
- Enter `ctest` as test command.
113+
- Leave `Test timeout` and `Clean command` empty.
114+
- Click on "Create project".
115+
116+
![Workflow](doc/2_create_project_dialog.png)
117+
118+
Note that we assume the build and test command to succeed if they end with exit code `0`. This is the default for most
119+
tools like CMake, CTest, Ninja, etc.
120+
121+
![Workflow](doc/3_project_overview.png)
122+
123+
We are now in the [project overview](http://127.0.0.1:5000/projects/1) and see the workflow Mutate++ will execute,
124+
consisting of the steps "build" and "test". We see that no files have been added yet, and also the patches overview is
125+
empty.
126+
127+
128+
### 3. Add a file
129+
130+
![Workflow](doc/4_add_file.png)
131+
132+
- Click on ["Add file"](http://127.0.0.1:5000/projects/1/files/add).
133+
- In the "Add file to project" dialog, enter the filename `/private/tmp/cmake-example/src/example.cpp`.
134+
- Click "Add file".
135+
136+
We are back in the [project overview](http://127.0.0.1:5000/projects/1), but see `example.cpp` added to the files. When
137+
we click on [example.cpp](http://127.0.0.1:5000/projects/1/files/1), we see the source code. Go back to the
138+
[project overview](http://127.0.0.1:5000/projects/1).
139+
140+
141+
### 4. Generate patches
142+
143+
![Workflow](doc/5_generate_patches.png)
144+
145+
- Next to "example.cpp", click on ["generate patches"](http://127.0.0.1:5000/projects/1/files/1/generate).
146+
- Leave "first line" and "last line" empty to mutate the whole file.
147+
- Right now, none of the check boxes are actually implemented, so you can ignore all of them - all mutations will be
148+
used by default.
149+
- Click on "Generate patches".
150+
151+
Back in the [project overview](http://127.0.0.1:5000/projects/1), you see that 14 patches have been generated. You can
152+
inspect them back clicking on ["14 patches"](http://127.0.0.1:5000/projects/1/patches) in the "Patches" section. In
153+
this overview, you see the individual patches with their line, kind, state and confirmation:
154+
155+
- The kind describes the nature of the patch, e.g. "lineDeletion" or "arithmeticOperator". If you click on a kind, the
156+
list of patches will be filteted accordingly.
157+
- The state describes whether the patch was analyzed so far; that is, whether the code has been mutated accordingly and
158+
the test suite has been executed. So far, all patches are incomplete.
159+
- The confirmation describes whether you have been evaluated the results so far. All patches are "unknown" so far.
160+
161+
![Workflow](doc/6_patch.png)
162+
163+
Let's now click on ["4"](http://127.0.0.1:5000/projects/1/patches/4) to have a look at the details of a patch:
164+
165+
- In the top, you see the actual patch: We see that the patch replaces `return f1 + f2;` by `return f1 * f2;`.
166+
- The description provides a summary of the patch.
167+
- You could set the confirmation, but this would not make sense as we have not executed the patch so far.
168+
- Finally, we see that no runs have been executed so far.
169+
170+
Go back to the [project overview](http://127.0.0.1:5000/projects/1).
171+
172+
173+
### 5. Execute patches
174+
175+
![Workflow](doc/7_queue.png)
176+
177+
Now it is time to actually apply the patches:
178+
179+
- In the top navigation, click on ["Queue"](http://127.0.0.1:5000/queue). We see the 14 patches from before.
180+
- Click on ["Resume"](http://127.0.0.1:5000/queue/start) to start the execution.
181+
- When you reload the page, you see that after a few seconds, the queue is empty.
182+
183+
![Workflow](doc/8_queue_running.png)
184+
185+
What happened? Mutate++ executed the workflow described above for each patch. That is, it applied the patch to the
186+
source file `/private/tmp/cmake-example/src/example.cpp`, executed the build command `make` in the working directory
187+
`/tmp/cmake-example/build`, and then executed the test command `ctest` in the same directory. As we have a trivial
188+
project with just 14 patches, this is just a matter of seconds.
189+
190+
Go back to the [project overview](http://127.0.0.1:5000/projects/1) by clicking on
191+
["Projects"](http://127.0.0.1:5000/projects) and ["Example project"](http://127.0.0.1:5000/projects/1).
192+
193+
194+
### 6. Evaluate results
195+
196+
![Workflow](doc/9_patches_overview.png)
197+
198+
The Patches second got more colorful now. We see a graph that describes the breakdown of the patches:
199+
200+
- 14 patches were genereated in total.
201+
- 13 patches were killed; meaning they have been detected by the test suite or the code did not compile.
202+
- 13 of these patches were killed due to a failure. There are other reasons a patch could have been killed which we
203+
describe later.
204+
- 1 patch survived, meaning the mutated source code could be compiled and tested without problems.
205+
206+
Let's investigate this by clicking on ["1 survived"](http://127.0.0.1:5000/projects/1/patches?patch_state=survived) to
207+
see the list of survived patches. We see that patch 14 survived. Click on
208+
["14"](http://127.0.0.1:5000/projects/1/patches/14).
209+
210+
In the patch overview, we see what happened:
211+
212+
- The patch deleted line 16 of file `/private/tmp/cmake-example/src/example.cpp`, resulting in function
213+
`multiply_numbers` not returning anything.
214+
- In the run overview, we see the individual steps of the workflow:
215+
- [Run 22](http://127.0.0.1:5000/projects/1/patches/14/runs/22) shows the output of the build command. Maybe some
216+
stricter compiler settings could have warned about the mutation, but instead the code was built successfully.
217+
- [Run 23](http://127.0.0.1:5000/projects/1/patches/14/runs/23) shows that also the test suite was executed
218+
successfully.
219+
220+
Why is this an issue? Because no one noticed that we deleted the multiplication! The easiest way to fix this is by
221+
adding a respective test case. In larger projects, we do not want to switch back and forth between evaluating patches
222+
and fixing the test suite, so we set the patch "confirm" and press "Submit".
223+
224+
In some cases, the patch would change the source code in ways that it is natural that the test suite would not
225+
detect it, e.g. in parts of the code that are skipped by preprocessor directives (e.g. with `#ifdef` commands) or when,
226+
the patch results in equivalent code. In that cases, set the patch to "ignore".
227+
228+
Later, you can filter to show only the
229+
[confirmed](http://127.0.0.1:5000/projects/1/patches?patch_state=survived&confirmation_state=confirmed) patches and
230+
adjust your test suite accordingly.
231+
232+
In the [project overview](http://127.0.0.1:5000/projects/1), you also get some statistics on the execution: We see that
233+
5 patches failed during the build, whereas 9 built successfully. From these 9, 8 failed the tests, and
234+
[1 patch](http://127.0.0.1:5000/projects/1/patches/14) succeeded.
235+
236+
237+
## Further features
238+
239+
- **Timeouts**. Mutating the code can create infinite loops. Therefore, it is wise to set a timeout for the tests in
240+
the "Create Project" dialog. Tests that take longer than this timeout are treated as if the test failed.
241+
- **Quickchecks**. A lot of test suites can be split into tests that run very quickly and the full test suite which
242+
may take many minutes to execute. In the "Create Project" dialog, you can define a "Quickcheck command" to execute
243+
this quicker test suite first. A lot of patches may be detected quicker this way.
244+
- **Cleaning up**. Some test suites may required cleaning up afterward. You can provide a "Clean command" in the
245+
"Create Project" dialog. It will be executed after processing each patch. Note that the example project implements
246+
a `make clean` command, but adding this as clean command would only increase the build times, because all source
247+
files would be re-compiled even if only one was changed.
248+
- **Hashing binaries**. Optimizing compilers may create exactly the same binary for programs that differ syntactically,
249+
but have the same semantics. Therefore, it can be helpful to calculate a hash of the generated binaries and compare
250+
it to reference values. If the hashes are the same, then you know the test suite will create the same result. To
251+
avoid waiting for such a false positive, Mutate++ can stop the evaluation of such patches if the test command or
252+
quickcheck command return exit code `77`.
253+
254+
255+
## Help!
256+
257+
Mutate++ is in a very early stage, and there is a lot to do. In particular, we are aware of severy limitations:
258+
259+
- Mutations are created very naively on a purely syntactial level and often result in code that fails compilation.
260+
Using, for instance, LLVM-based tools could help to use more type information to create mutations which are more
261+
likely to result in valid C++ programs.
262+
- The web app has a terrible UX, and should be overworked by someone who knows more about this...
263+
264+
That said, pull requests and issues are more than welcome!
265+
266+
267+
## Used third-party tools
268+
269+
Mutate++ contains the following libraries for the frontend:
270+
271+
- [Bootstrap](http://getbootstrap.com) for the frontend components
272+
- [Font Awesome](http://fontawesome.io) for the icons
273+
- [highlight.js](https://highlightjs.org) for source code syntax highlighting
274+
- [jQuery](https://jquery.com) for some custom JavaScript code
275+
276+
The web app uses [Flask](http://flask.pocoo.org) and [SQLAlchemy](https://www.sqlalchemy.org), and a lot of
277+
[other packages](requirements.txt).
278+
279+
280+
## License
281+
282+
<img align="right" src="http://opensource.org/trademarks/opensource/OSI-Approved-License-100x137.png">
283+
284+
Mutate++ is licensed under the [MIT License](http://opensource.org/licenses/MIT):
285+
286+
Copyright &copy; 2017 [Niels Lohmann](http://nlohmann.me)
287+
288+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
289+
documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
290+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
291+
persons to whom the Software is furnished to do so, subject to the following conditions:
292+
293+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
294+
Software.
295+
296+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
297+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
298+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
299+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

app/__init__.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# coding=utf-8
2+
3+
from flask import Flask
4+
from flask_compress import Compress
5+
from flask_sqlalchemy import SQLAlchemy
6+
from flask_humanize import Humanize
7+
8+
app = Flask(__name__)
9+
app.config.from_object('config')
10+
11+
Compress(app)
12+
13+
db = SQLAlchemy(app)
14+
15+
humanize = Humanize(app)
16+
17+
from app import views, models

app/config.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# coding=utf-8
2+
3+
import os
4+
basedir = os.path.abspath(os.path.dirname(__file__))
5+
6+
WTF_CSRF_ENABLED = True
7+
SECRET_KEY = 'you-will-never-guess'
8+
9+
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
10+
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
11+
SQLALCHEMY_TRACK_MODIFICATIONS = False
12+
13+
ITEMS_PER_PAGE = 20
14+
15+
TEMPLATES_AUTO_RELOAD = True

app/db_repository/README

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
This is a database migration repository.
2+
3+
More information at
4+
http://code.google.com/p/sqlalchemy-migrate/

app/db_repository/__init__.py

Whitespace-only changes.

app/db_repository/manage.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python
2+
from migrate.versioning.shell import main
3+
4+
if __name__ == '__main__':
5+
main()

app/db_repository/migrate.cfg

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[db_settings]
2+
# Used to identify which repository this database is versioned under.
3+
# You can use the name of your project.
4+
repository_id=database repository
5+
6+
# The name of the database table used to track the schema version.
7+
# This name shouldn't already be used by your project.
8+
# If this is changed once a database is under version control, you'll need to
9+
# change the table name in each database too.
10+
version_table=migrate_version
11+
12+
# When committing a change script, Migrate will attempt to generate the
13+
# sql for all supported databases; normally, if one of them fails - probably
14+
# because you don't have that database installed - it is ignored and the
15+
# commit continues, perhaps ending successfully.
16+
# Databases in this list MUST compile successfully during a commit, or the
17+
# entire commit will fail. List the databases your application will actually
18+
# be using to ensure your updates to that database work properly.
19+
# This must be a list; example: ['postgres','sqlite']
20+
required_dbs=[]
21+
22+
# When creating new change scripts, Migrate will stamp the new script with
23+
# a version number. By default this is latest_version + 1. You can set this
24+
# to 'true' to tell Migrate to use the UTC timestamp instead.
25+
use_timestamp_numbering=False

app/db_repository/versions/__init__.py

Whitespace-only changes.

app/forms.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# coding=utf-8
2+
3+
from flask_wtf import FlaskForm
4+
import wtforms
5+
from wtforms.validators import DataRequired, Optional
6+
7+
8+
class CreateProjectForm(FlaskForm):
9+
name = wtforms.StringField('name', validators=[DataRequired()])
10+
workdir = wtforms.StringField('workdir', validators=[DataRequired()])
11+
build_command = wtforms.StringField('build_command', validators=[DataRequired()])
12+
quickcheck_command = wtforms.StringField('quickcheck_command', validators=[Optional()])
13+
quickcheck_timeout = wtforms.FloatField('quickcheck_timeout', validators=[Optional()])
14+
test_command = wtforms.StringField('test_command', validators=[DataRequired()])
15+
test_timeout = wtforms.FloatField('test_timeout', validators=[Optional()])
16+
clean_command = wtforms.StringField('clean_command', validators=[Optional()])
17+
18+
19+
class CreateFileForm(FlaskForm):
20+
filename = wtforms.StringField('filename', validators=[DataRequired()])
21+
22+
23+
class SetConfirmationForm(FlaskForm):
24+
confirmation = wtforms.RadioField('comfirmation', choices=[('unknown', 'unknown'),
25+
('confirmed', 'confirmed'),
26+
('ignored', 'ignored')])

0 commit comments

Comments
 (0)