Automatically extracting inline tests from unit tests and executing these inline tests with itest.
This repo contains the code and data for producing the experiments in ExLi.
- "Declare" part
itest
In our experiments, we use itest(test_source, target_stmt_line_number), to represent the test source and the line number of the target statement in original file (note that after adding inline tests to the original file, the line numbers of target statements change).
For example, itest("randoop", 57) means that the test source is Randoop generated tests, and the target statement starts at line 57 in the original file.
-
"Assign" part
given(var, value) -
"Assert" part
assert(var, value)
raninline: This directory constains the source code of TargetStmtFinder, VariablesFinder, Instrumenter, Collector, Round1Reducer, and InlineTestConstructor.
python: This directory contains the scripts of running ExLi end-to-end and generates figures.
- Docker
- Disk space: 10GB
In the docker, the following tools/dependencies are installed:
- Conda latest version
- Python 3.9 (or later)
- Java 8, JUnit 4
- Maven 3.8.3 (or later)
Build a docker image
docker build -t exli .
docker run -it exli /bin/bash
In the docker, create a Python environment named exli
cd exli/python && bash prepare-conda-env.sh
conda activate exli
- For each command, append
--helpto see the usage and options.
For example,
python -m exli.main find_target_stmts --help
usage: main.py [options] find_target_stmts [-h] [--config CONFIG] [--print_config[=flags]]
--project_name PROJECT_NAME --sha SHA
--target_stmts_path TARGET_STMTS_PATH
Find target statements for a project.
...
--project_name PROJECT_NAME
org_repo, e.g., https://github.com/Bernardo-MG/velocity-config-tool, the project name is
Bernardo-MG_velocity-config-tool (required, type: str)
--sha SHA commit hash (required, type: str)
--target_stmts_path TARGET_STMTS_PATH
path to store the target statements (required, type: str)- For each command, the argument and value can be seperated by
=or space. For example,--project_name=Bernardo-MG_velocity-config-toolor--project_name Bernardo-MG_velocity-config-toolare both valid. - To run on a specific project, replace
Bernardo-MG_velocity-config-toolwith the project name and26226f5with the commit hash. Notice that only projects that are in evaluated projects can be run with the command that only requiretest_project_name.
It will help EvoSuite reduce the search scope. Otherwise, EvoSuite will generate tests on the whole project, which may take a long time (~2 min * number of classes). Also, the second round reducer will generate mutants for these target statements.
In exli/python directory
python -m exli.main find_target_stmts --project_name=Bernardo-MG_velocity-config-tool --sha=26226f5 --target_stmts_path=${HOME}/exli/results/target-stmt/Bernardo-MG_velocity-config-tool-26226f5.txtThe generated target statements are in
${HOME}/exli/results/target-stmt/Bernardo-MG_velocity-config-tool-26226f5.txt
Alternatively, to use the default output file path
python -m exli.main batch_find_target_stmts --test_project_name=Bernardo-MG_velocity-config-tool
There are three target statements found in the project Bernardo-MG_velocity-config-tool at commit 26226f5:
target stmt string;/home/itdocker/exli/_downloads/Bernardo-MG_velocity-config-tool/src/main/java/com/bernardomg/velocity/tool/ConfigTool.java;195;null;;null
target stmt string;/home/itdocker/exli/_downloads/Bernardo-MG_velocity-config-tool/src/main/java/com/bernardomg/velocity/tool/ConfigTool.java;200;null;;null
target stmt string;/home/itdocker/exli/_downloads/Bernardo-MG_velocity-config-tool/src/main/java/com/bernardomg/velocity/tool/ConfigTool.java;288;null;;nullIn exli/python directory
python -m exli.main run --project_name=Bernardo-MG_velocity-config-tool --sha=26226f5 --randoop=True --randoop_tl=100 --dev=True --evosuite=True --evosuite_tl=120 --seed=42 --log_path=${HOME}/exli/log/raninline.logThe generated inline tests are in
${HOME}/exli/all-tests/Bernardo-MG_velocity-config-tool-26226f5
Alternatively, to use the default setting for test generation and output dirs path
python -m exli.main batch_run --test_project_name=Bernardo-MG_velocity-config-tool
In exli/python directory
Execute r0 inline tests:
python -m exli.main run_inline_tests --project_name=Bernardo-MG_velocity-config-tool --sha=26226f5 --generated_tests_dir=${HOME}/exli/r0-tests/Bernardo-MG_velocity-config-tool-26226f5 --inline_tests_dir=${HOME}/exli/r0-its/Bernardo-MG_velocity-config-tool-26226f5 --inlinetest_report_path=${HOME}/exli/results/r0-its-report/Bernardo-MG_velocity-config-tool-26226f5.json --cached_objects_dir=${HOME}/exli/r0-tests/Bernardo-MG_velocity-config-tool-26226f5/.inlinegen --deps_file=${HOME}/exli/generated-tests/Bernardo-MG_velocity-config-tool-26226f5/deps.txt --parse_inline_tests=True --log_path=${HOME}/exli/log/run-its.logExecute r1 inline tests:
python -m exli.main run_inline_tests --project_name=Bernardo-MG_velocity-config-tool --sha=26226f5 --generated_tests_dir=${HOME}/exli/r1-tests/Bernardo-MG_velocity-config-tool-26226f5 --inline_tests_dir=${HOME}/exli/r1-its/Bernardo-MG_velocity-config-tool-26226f5 --inlinetest_report_path=${HOME}/exli/results/r1-its-report/Bernardo-MG_velocity-config-tool-26226f5.json --cached_objects_dir=${HOME}/exli/r0-tests/Bernardo-MG_velocity-config-tool-26226f5/.inlinegen --deps_file=${HOME}/exli/generated-tests/Bernardo-MG_velocity-config-tool-26226f5/deps.txt --parse_inline_tests=True --log_path=${HOME}/exli/log/run-its.logThese two commands run the inline tests and generate the execution report at
${HOME}/exli/results/r1-its-report/Bernardo-MG_velocity-config-tool-26226f5.jsonand${HOME}/exli/results/r0-its-report/Bernardo-MG_velocity-config-tool-26226f5.json
r1-testsandr0-testsstore the source code with inline tests,r1-itsandr0-itsstore the inline tests parsed by itest frameworkTo generate the report for all inline tests, replace
r1withr0in the generated tests dir, inline tests dir and execution report (${HOME}/exli/r1-its->${HOME}/exli/r0-its,${HOME}/exli/results/r1-its-report->${HOME}/exli/results/r0-its-reportand${HOME}/exli/results/r1-its-reportto${HOME}/exli/results/r0-its-report)The difference between "r0-its-report" and "r1-its-report" is that the "r0-its-report" contains all the inline tests, while the "r1-its-report" contains the inline tests that are reduced by the Round 1 Reducer (based on coverage)
Alternatively, to use the default setting for output dirs
python -m exli.main batch_run_inline_tests --test_project_name=Bernardo-MG_velocity-config-tool, this will run the inline tests for both reduced and all inline tests
After running the inline tests, we can analyze the inline tests reports to get all passed and failed inline tests.
python -m exli.main analyze_inline_tests_reports --inline_test_type=r0python -m exli.main analyze_inline_tests_reports --inline_test_type=r1The report shows the number of tests, errors, failures, and time. For example,
{
"testsuite": {
"@errors": "0",
"@failures": "0",
"@hostname": "bafe0a4bce5a",
"@name": "JUnit Jupiter",
"@skipped": "0",
"@tests": "6",
"@time": "0.042",
...
}
}It is possible to see the message "Some inline tests failed for {project_name} {sha} during execution" because not all generated inline tests can be compiled or executed successfully. For example, if the user-defined class does not override the
toString()method, the serialized object will contain the class name and the hash code, and the new object will not be equal to the original object when we run the inline tests and compare the objects (the collected value does not equal to the runtime value).If there are inline tests failed because of compilation, we directly remove these failed inline tests, the log file
${HOME}/exli/results/r1-its-report/Bernardo-MG_velocity-config-tool-comp-failed-tests.txtstores the failed inline tests and the reason why they failed (This project does not have compilation failed inline tests so this file does not exist).If inline tests failed because of execution, run the following command to remove the failed inline tests:
python -m exli.main remove_failed_tests --inline_test_type=r1(orr0)Re-generate test reports:
python -m exli.main batch_run_inline_tests --test_project_name=Bernardo-MG_velocity-config-tool
The generated execution result can be found at
${HOME}/exli/results/r1-its-report/Bernardo-MG_velocity-config-tool-26226f5.json
${HOME}/exli/results/r0-its-report/Bernardo-MG_velocity-config-tool-26226f5.json
In exli/python directory
Generate mutants:
python -m exli.main generate_mutants --project_name=Bernardo-MG_velocity-config-tool --sha=26226f5 --target_stmts_path=${HOME}/exli/results/target-stmt/Bernardo-MG_velocity-config-tool-26226f5.txt --output_path=${HOME}/exli/results/mutants/Bernardo-MG_velocity-config-tool-26226f5-universalmutator.jsonAlternatively, to use the default settings
python -m exli.main batch_generate_mutants --test_project_name=Bernardo-MG_velocity-config-tool
The generated mutants are in ${HOME}/exli/results/mutants/Bernardo-MG_velocity-config-tool-26226f5-universalmutator.json
Run mutation analysis:
python -m exli.eval run_tests_with_mutants --project_name=Bernardo-MG_velocity-config-tool --sha=26226f5 --test_types="['r0', 'r1']" --mutator=universalmutator --log_path=${HOME}/exli/log/run-tests-with-mutants.logAlternatively, to use the default settings
python -m exli.eval batch_run_tests_with_mutants --test_project_name=Bernardo-MG_velocity-config-tool
The generated mutation analysis report are in
${HOME}/exli/results/mutants-eval-results/Bernardo-MG_velocity-config-tool-26226f5-universalmutator-r0.json
and
${HOME}/exli/results/mutants-eval-results/Bernardo-MG_velocity-config-tool-26226f5-universalmutator-r1.json
python -m exli.eval get_r2_tests --project_name=Bernardo-MG_velocity-config-tool --sha=26226f5 --mutator=universalmutator --algo=greedy --output_path=${HOME}/exli/results/r2/Bernardo-MG_velocity-config-tool-26226f5-universalmutator-greedy.txt${HOME}/exli/results/minimized stores the minimized tests by 4
different algorithms: Greedy, GE, GRE,
HGS, which are called r2 tests in the paper. For
example, the minimized tests by greedy algorithm for the project
Bernardo-MG_velocity-config-tool at commit 26226f5 are stored in
${HOME}/exli/results/minimized/Bernardo-MG_velocity-config-tool-26226f5-universalmutator-greedy.txt
Bernardo-MG_velocity-config-tool#com.bernardomg.velocity.tool.ConfigTool_200Test#testLine204()#r1
Bernardo-MG_velocity-config-tool#com.bernardomg.velocity.tool.ConfigTool_288Test#testLine290()#r1
Bernardo-MG_velocity-config-tool#com.bernardomg.velocity.tool.ConfigTool_288Test#testLine302()#r0Tests that end with #r0 are from R0 tests, end with #r1 are from R1 tests. These 3 tests are the minimized tests (r2 tests) by the greedy algorithm.
${HOME}/exli/results/itests-without-mutants stores the inline tests whose target statements do not have mutants. For example, the inline tests for the project Bernardo-MG_velocity-config-tool at commit 26226f5 are stored in ${HOME}/exli/results/itests-without-mutants/Bernardo-MG_velocity-config-tool-26226f5-universalmutator.txt
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;195;197
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;195;198
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;195;199After merging ${HOME}/exli/results/minimized/Bernardo-MG_velocity-config-tool-26226f5-universalmutator-greedy.txt and ${HOME}/exli/results/itests-without-mutants/Bernardo-MG_velocity-config-tool-26226f5-universalmutator.txt, the final r2 tests are stored in ${HOME}/exli/results/r2/Bernardo-MG_velocity-config-tool-26226f5-universalmutator-greedy.txt
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;200;204;r1
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;288;290;r1
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;288;302;r0
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;195;197;r1
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;195;198;r1
Bernardo-MG_velocity-config-tool;com.bernardomg.velocity.tool.ConfigTool;195;199;r1Running
python -m exli.eval batch_get_r2_testswill merge all the minized tests with greedy algorithm into one file${HOME}/exli/results/r2-universalmutator-greedy-passed-tests.txt
See the Reproduce the results for more details.
If you have used ExLi in a research project, please cite the research paper in any related publication:
Title: Extracting Inline Tests from Unit Tests
Authors: Yu Liu, Pengyu Nie, Anna Guo, Milos Gligoric, Owolabi Legunsen
@inproceedings{LiuISSTA23EXLI,
title = {Extracting Inline Tests from Unit Tests},
author = {Yu Liu and Pengyu Nie and Anna Guo and Milos Gligoric and Owolabi Legunsen},
pages = {1--13},
booktitle = {Proceedings of the 32nd ACM SIGSOFT International Symposium on Software Testing and Analysis},
year = {2023},
}@inproceedings{LiuFSE24EXLI,
title = {ExLi: An Inline-Test Generation Tool for Java},
author = {Yu Liu and Aditya Thimmaiah and Owolabi Legunsen and Milos Gligoric},
pages = {1--5},
booktitle = {Proceedings of the 32nd ACM Joint European Software Engineering Conference and Symposium on the Foundations of Software Engineering (Demonstrations)},
year = {2024},
}