Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ by the test suite, the "survived" mutation can be used to create an additional t
- [Installation](#installation)
- [Example](#example)
- [Further features](#further-features)
- [Command-line tools](#command-line-tools)
- [Help!](#help)
- [Used third-party tools](#used-third-party-tools)
- [License](#license)
Expand Down Expand Up @@ -252,6 +253,43 @@ In the [project overview](http://127.0.0.1:5000/projects/1), you also get some s
quickcheck command return exit code `77`.


## Command-line tools

Mutate++ provides some command-line scripts to facilitate its usage without the web interface.
These scripts can be found in the `cli` directory, and should be run from the root directory.

### `create_project.py`
This script will create a project and add it to the database.

Example usage:
```bash
venv/bin/python3 cli/create_project.py --name "Example project" --workdir "/tmp/cmake-example/build" --build-command "make" --test-command "ctest"
```

### `add_files.py`
This script will add files to an existing project.

Example usage:
```bash
venv/bin/python3 cli/add_files.py --project "Example project" /tmp/cmake-example/src/*.cpp
```

### `generate_patches.py`
This script will generate patches for all files in a project.

Example usage:
```bash
venv/bin/python3 cli/generate_patches.py --project "Example project"
```

### `queue_control.py`
This script allows you to control the queue and view its state.

Example usage:
```bash
venv/bin/python3 cli/queue_control.py start
```

## Help!

Mutate++ is in a very early stage, and there is a lot to do. In particular, we are aware of severe limitations:
Expand Down
2 changes: 0 additions & 2 deletions app/utils/SourceFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ def __init__(self, file: File, first_line, last_line):
self.filename = file.filename
self.full_content = [x.rstrip() for x in file.content.split('\n')]

print(first_line, last_line)

# the line numbers stored here are human-readable; to use them as indices to
# self.full_content, we need to subtract -1
self.first_line = first_line
Expand Down
69 changes: 69 additions & 0 deletions cli/add_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python
# coding=utf-8
# PYTHON_ARGCOMPLETE_OK

import sys
from argparse import ArgumentParser
from pathlib import Path

# Allow this script to be used from the parent directory
sys.path.append(".")

from app import db
from app.models import Project, File


def main():
# Parse arguments
argument_parser = ArgumentParser(description="Add files to a project.")
argument_parser.add_argument(
"--project", type=str, required=True,
help="The name of the project."
)
argument_parser.add_argument(
'files', type=str, metavar="FILE", nargs='+',
help="A file to be added to the project."
)
arguments = argument_parser.parse_args()

# Verify that the project exists
project_query = Project.query.filter(Project.name == arguments.project)
if project_query.count() != 1:
print(f"Project '{arguments.project}' doesn't exist.", file=sys.stderr)
exit(1)

# Retrieve project id
project_id = project_query.first().id

for arg_filename in arguments.files:
file_path: Path = Path(arg_filename)
filename = file_path.absolute().as_posix()

# Verify that the file exists
if not file_path.exists():
print(f"File '{file_path}' doesn't exist.", file=sys.stderr)
exit(2)

# Verify that the file isn't already added to the project
file_contents = file_path.read_text()

# Add the file to the db
file = File(
filename=filename,
content=file_contents,
project_id=project_id
)

if File.query.filter(File.project_id == project_id).filter(File.filename == filename).count() > 0:
print(f"Skipping file '{file_path.name}' since it is already added to project '{arguments.project}'.")
continue

db.session.add(file)
db.session.commit()

print(f"File '{file_path.name}' added successfully.")
exit(0)


if __name__ == "__main__":
main()
76 changes: 76 additions & 0 deletions cli/create_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python
# coding=utf-8
# PYTHON_ARGCOMPLETE_OK

import sys
from argparse import ArgumentParser

# Allow this script to be used from the parent directory
sys.path.append(".")

from app import db
from app.models import Project


def main():
# Parse arguments
argument_parser = ArgumentParser(description="Create a new project.")
argument_parser.add_argument(
"--name", type=str, required=True,
help="The name of the project."
)
argument_parser.add_argument(
"--workdir", type=str, required=True,
help="The working directory in which all commands will be executed."
)
argument_parser.add_argument(
"--build-command", type=str, required=True,
help="The build command to use."
)
argument_parser.add_argument(
"--quickcheck-command", type=str, required=False, default='',
help="The quickcheck command to use."
)
argument_parser.add_argument(
"--quickcheck-timeout", type=int, required=False,
help="The timeout of the quickcheck command."
)
argument_parser.add_argument(
"--test-command", type=str, required=True,
help="The test command to use."
)
argument_parser.add_argument(
"--test-timeout", type=int, required=False,
help="The timeout of the test command."
)
argument_parser.add_argument(
"--clean-command", type=str, required=False, default='',
help="The clean command to use."
)
arguments = argument_parser.parse_args()

# Verify that a project with the same name doesn't exist yet
if Project.query.filter(Project.name == arguments.name).count() > 0:
print(f"Project '{arguments.name}' already exists", file=sys.stderr)
exit(1)

# Add the project to the DB
project = Project(
name=arguments.name,
workdir=arguments.workdir,
build_command=arguments.build_command,
quickcheck_command=arguments.quickcheck_command,
quickcheck_timeout=arguments.quickcheck_timeout,
test_command=arguments.test_command,
test_timeout=arguments.test_timeout,
clean_command=arguments.clean_command
)
db.session.add(project)
db.session.commit()

print(f"Project '{project.name}' created successfully.")
exit(0)


if __name__ == "__main__":
main()
48 changes: 48 additions & 0 deletions cli/generate_patches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python
# coding=utf-8
# PYTHON_ARGCOMPLETE_OK

import sys
from argparse import ArgumentParser

# Allow this script to be used from the parent directory
sys.path.append(".")

from app.models import Project, File
from app.utils.SourceFile import SourceFile


def main():
# Parse arguments
argument_parser = ArgumentParser(description="Generate patches.")
argument_parser.add_argument(
"--project", type=str, required=True,
help="The name of the project. If not provided, all projects will be processed."
)
# TODO allow selection of first/last line + which patches to generate
arguments = argument_parser.parse_args()

# Verify that the project exists
project_query = Project.query.filter(Project.name == arguments.project)
if project_query.count() == 0:
print(f"Project '{arguments.project}' doesn't exist.", file=sys.stderr)
exit(1)

# Retrieve all files
files = File.query.filter(File.project_id == project_query.first().id).all()

if len(files) == 0:
print("No files found to process.")
exit(2)

for file in files:
print(f"Generating patches for '{file.filename}'...")
source_file = SourceFile(file, 1, -1)
source_file.generate_patches()

print("Done")
exit(0)


if __name__ == "__main__":
main()
42 changes: 42 additions & 0 deletions cli/queue_control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env python
# coding=utf-8
# PYTHON_ARGCOMPLETE_OK

import sys
from argparse import ArgumentParser
from urllib import request

# Allow this script to be used from the parent directory
sys.path.append(".")

from app.models import Patch


# TODO retrieve the actual server and port from the application

def main():
# Parse argument
argument_parser = ArgumentParser(description="Control the Mutate++ queue.")
argument_parser.add_argument(
"action", choices=['start', 'stop', 'status'],
help="The queue action to be done."
)
arguments = argument_parser.parse_args()

if arguments.action == 'start':
request.urlopen("http://127.0.0.1:5000/queue/start")
elif arguments.action == 'stop':
request.urlopen("http://127.0.0.1:5000/queue/stop")
elif arguments.action == 'status':
incomplete_patches = Patch.query.filter(Patch.state == 'incomplete').count()
all_patches = Patch.query.count()
if all_patches == 0:
print("No patches generated.")
exit(0)
finished_patches = all_patches - incomplete_patches
percentage = 100 * ((all_patches - incomplete_patches) / all_patches)
print(f"Patch {finished_patches} / {all_patches} ({percentage:.0f}%)")


if __name__ == "__main__":
main()