Skip to content

Commit de76704

Browse files
authored
Merge pull request nlohmann#24 from dirtcrusher/feature/cli_scripts
Added some CLI scripts
2 parents 96b6ca5 + 9d3cc61 commit de76704

File tree

6 files changed

+273
-2
lines changed

6 files changed

+273
-2
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ by the test suite, the "survived" mutation can be used to create an additional t
99
- [Installation](#installation)
1010
- [Example](#example)
1111
- [Further features](#further-features)
12+
- [Command-line tools](#command-line-tools)
1213
- [Help!](#help)
1314
- [Used third-party tools](#used-third-party-tools)
1415
- [License](#license)
@@ -252,6 +253,43 @@ In the [project overview](http://127.0.0.1:5000/projects/1), you also get some s
252253
quickcheck command return exit code `77`.
253254

254255

256+
## Command-line tools
257+
258+
Mutate++ provides some command-line scripts to facilitate its usage without the web interface.
259+
These scripts can be found in the `cli` directory, and should be run from the root directory.
260+
261+
### `create_project.py`
262+
This script will create a project and add it to the database.
263+
264+
Example usage:
265+
```bash
266+
venv/bin/python3 cli/create_project.py --name "Example project" --workdir "/tmp/cmake-example/build" --build-command "make" --test-command "ctest"
267+
```
268+
269+
### `add_files.py`
270+
This script will add files to an existing project.
271+
272+
Example usage:
273+
```bash
274+
venv/bin/python3 cli/add_files.py --project "Example project" /tmp/cmake-example/src/*.cpp
275+
```
276+
277+
### `generate_patches.py`
278+
This script will generate patches for all files in a project.
279+
280+
Example usage:
281+
```bash
282+
venv/bin/python3 cli/generate_patches.py --project "Example project"
283+
```
284+
285+
### `queue_control.py`
286+
This script allows you to control the queue and view its state.
287+
288+
Example usage:
289+
```bash
290+
venv/bin/python3 cli/queue_control.py start
291+
```
292+
255293
## Help!
256294

257295
Mutate++ is in a very early stage, and there is a lot to do. In particular, we are aware of severe limitations:

app/utils/SourceFile.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ def __init__(self, file: File, first_line, last_line):
1414
self.filename = file.filename
1515
self.full_content = [x.rstrip() for x in file.content.split('\n')]
1616

17-
print(first_line, last_line)
18-
1917
# the line numbers stored here are human-readable; to use them as indices to
2018
# self.full_content, we need to subtract -1
2119
self.first_line = first_line

cli/add_files.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
# PYTHON_ARGCOMPLETE_OK
4+
5+
import sys
6+
from argparse import ArgumentParser
7+
from pathlib import Path
8+
9+
# Allow this script to be used from the parent directory
10+
sys.path.append(".")
11+
12+
from app import db
13+
from app.models import Project, File
14+
15+
16+
def main():
17+
# Parse arguments
18+
argument_parser = ArgumentParser(description="Add files to a project.")
19+
argument_parser.add_argument(
20+
"--project", type=str, required=True,
21+
help="The name of the project."
22+
)
23+
argument_parser.add_argument(
24+
'files', type=str, metavar="FILE", nargs='+',
25+
help="A file to be added to the project."
26+
)
27+
arguments = argument_parser.parse_args()
28+
29+
# Verify that the project exists
30+
project_query = Project.query.filter(Project.name == arguments.project)
31+
if project_query.count() != 1:
32+
print(f"Project '{arguments.project}' doesn't exist.", file=sys.stderr)
33+
exit(1)
34+
35+
# Retrieve project id
36+
project_id = project_query.first().id
37+
38+
for arg_filename in arguments.files:
39+
file_path: Path = Path(arg_filename)
40+
filename = file_path.absolute().as_posix()
41+
42+
# Verify that the file exists
43+
if not file_path.exists():
44+
print(f"File '{file_path}' doesn't exist.", file=sys.stderr)
45+
exit(2)
46+
47+
# Verify that the file isn't already added to the project
48+
file_contents = file_path.read_text()
49+
50+
# Add the file to the db
51+
file = File(
52+
filename=filename,
53+
content=file_contents,
54+
project_id=project_id
55+
)
56+
57+
if File.query.filter(File.project_id == project_id).filter(File.filename == filename).count() > 0:
58+
print(f"Skipping file '{file_path.name}' since it is already added to project '{arguments.project}'.")
59+
continue
60+
61+
db.session.add(file)
62+
db.session.commit()
63+
64+
print(f"File '{file_path.name}' added successfully.")
65+
exit(0)
66+
67+
68+
if __name__ == "__main__":
69+
main()

cli/create_project.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
# PYTHON_ARGCOMPLETE_OK
4+
5+
import sys
6+
from argparse import ArgumentParser
7+
8+
# Allow this script to be used from the parent directory
9+
sys.path.append(".")
10+
11+
from app import db
12+
from app.models import Project
13+
14+
15+
def main():
16+
# Parse arguments
17+
argument_parser = ArgumentParser(description="Create a new project.")
18+
argument_parser.add_argument(
19+
"--name", type=str, required=True,
20+
help="The name of the project."
21+
)
22+
argument_parser.add_argument(
23+
"--workdir", type=str, required=True,
24+
help="The working directory in which all commands will be executed."
25+
)
26+
argument_parser.add_argument(
27+
"--build-command", type=str, required=True,
28+
help="The build command to use."
29+
)
30+
argument_parser.add_argument(
31+
"--quickcheck-command", type=str, required=False, default='',
32+
help="The quickcheck command to use."
33+
)
34+
argument_parser.add_argument(
35+
"--quickcheck-timeout", type=int, required=False,
36+
help="The timeout of the quickcheck command."
37+
)
38+
argument_parser.add_argument(
39+
"--test-command", type=str, required=True,
40+
help="The test command to use."
41+
)
42+
argument_parser.add_argument(
43+
"--test-timeout", type=int, required=False,
44+
help="The timeout of the test command."
45+
)
46+
argument_parser.add_argument(
47+
"--clean-command", type=str, required=False, default='',
48+
help="The clean command to use."
49+
)
50+
arguments = argument_parser.parse_args()
51+
52+
# Verify that a project with the same name doesn't exist yet
53+
if Project.query.filter(Project.name == arguments.name).count() > 0:
54+
print(f"Project '{arguments.name}' already exists", file=sys.stderr)
55+
exit(1)
56+
57+
# Add the project to the DB
58+
project = Project(
59+
name=arguments.name,
60+
workdir=arguments.workdir,
61+
build_command=arguments.build_command,
62+
quickcheck_command=arguments.quickcheck_command,
63+
quickcheck_timeout=arguments.quickcheck_timeout,
64+
test_command=arguments.test_command,
65+
test_timeout=arguments.test_timeout,
66+
clean_command=arguments.clean_command
67+
)
68+
db.session.add(project)
69+
db.session.commit()
70+
71+
print(f"Project '{project.name}' created successfully.")
72+
exit(0)
73+
74+
75+
if __name__ == "__main__":
76+
main()

cli/generate_patches.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
# PYTHON_ARGCOMPLETE_OK
4+
5+
import sys
6+
from argparse import ArgumentParser
7+
8+
# Allow this script to be used from the parent directory
9+
sys.path.append(".")
10+
11+
from app.models import Project, File
12+
from app.utils.SourceFile import SourceFile
13+
14+
15+
def main():
16+
# Parse arguments
17+
argument_parser = ArgumentParser(description="Generate patches.")
18+
argument_parser.add_argument(
19+
"--project", type=str, required=True,
20+
help="The name of the project. If not provided, all projects will be processed."
21+
)
22+
# TODO allow selection of first/last line + which patches to generate
23+
arguments = argument_parser.parse_args()
24+
25+
# Verify that the project exists
26+
project_query = Project.query.filter(Project.name == arguments.project)
27+
if project_query.count() == 0:
28+
print(f"Project '{arguments.project}' doesn't exist.", file=sys.stderr)
29+
exit(1)
30+
31+
# Retrieve all files
32+
files = File.query.filter(File.project_id == project_query.first().id).all()
33+
34+
if len(files) == 0:
35+
print("No files found to process.")
36+
exit(2)
37+
38+
for file in files:
39+
print(f"Generating patches for '{file.filename}'...")
40+
source_file = SourceFile(file, 1, -1)
41+
source_file.generate_patches()
42+
43+
print("Done")
44+
exit(0)
45+
46+
47+
if __name__ == "__main__":
48+
main()

cli/queue_control.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
# PYTHON_ARGCOMPLETE_OK
4+
5+
import sys
6+
from argparse import ArgumentParser
7+
from urllib import request
8+
9+
# Allow this script to be used from the parent directory
10+
sys.path.append(".")
11+
12+
from app.models import Patch
13+
14+
15+
# TODO retrieve the actual server and port from the application
16+
17+
def main():
18+
# Parse argument
19+
argument_parser = ArgumentParser(description="Control the Mutate++ queue.")
20+
argument_parser.add_argument(
21+
"action", choices=['start', 'stop', 'status'],
22+
help="The queue action to be done."
23+
)
24+
arguments = argument_parser.parse_args()
25+
26+
if arguments.action == 'start':
27+
request.urlopen("http://127.0.0.1:5000/queue/start")
28+
elif arguments.action == 'stop':
29+
request.urlopen("http://127.0.0.1:5000/queue/stop")
30+
elif arguments.action == 'status':
31+
incomplete_patches = Patch.query.filter(Patch.state == 'incomplete').count()
32+
all_patches = Patch.query.count()
33+
if all_patches == 0:
34+
print("No patches generated.")
35+
exit(0)
36+
finished_patches = all_patches - incomplete_patches
37+
percentage = 100 * ((all_patches - incomplete_patches) / all_patches)
38+
print(f"Patch {finished_patches} / {all_patches} ({percentage:.0f}%)")
39+
40+
41+
if __name__ == "__main__":
42+
main()

0 commit comments

Comments
 (0)