How to Extend APRConfig to Support More Datasets, Fault Localizers, Patch Generation Algorithms, and Patch Validators?
Publicly executable frameworks that integrate multiple datasets or APR tools could serve as an infrastructure and benefits follow-up research in the community. Here We aim to contribute a new executable framework to facilitate a fairer APR evaluation. While it costs much engineering effort to construct such an executable framework, we plan to continuously maintain this repository. Accordingly, we provide the guideline on how to extend APRConfig with more datasets, fault localizers, patch generation algorithms, and patch validators. Any question or contribution is much welcomed.
-
Add the dataset directory into the
datasetsfolder. -
Modify the
APRConfig/dataset/Dataset_factory.pyas highlighted in the following comments:
import logging
import Defects4j
import Bears # 1. add import
import Quixbugs
logger = logging.getLogger()
class DatasetFactory():
def create_dataset(self, dataset_name):
if dataset_name.lower() == "defects4j":
return Defects4j.Defects4j()
elif dataset_name.lower() == "bears": # 2. add an else if stmt
return Bears.Bears()
elif dataset_name.lower() == "quixbugs":
return Quixbugs.Quixbugs()
else:
raise Exception(f"unknown dataset name: {dataset_name}")- Add a new file (e.g.,
Bears.pyfor the Bears dataset) into theAPRConfig/dataset/folder. The content is:
import logging
import os
import json
import shutil
import Dataset
from dataset import Bug, Bears_util
import Config
from utils import Cmd_util, File_util
logger = logging.getLogger()
class Bears(Dataset.Dataset):
def __init__(self):
self.name = self.__class__.__name__.lower()
super().__init__(self.name)
def get_bug(self, bug_id):
bug_id = bug_id.replace("_", "-")
separator = "-"
splitted = bug_id.split(separator)
project = splitted[0]
if len(splitted) < 3:
project = "-".join(splitted[:-1])
else:
patched = splitted[-1]
buggy = splitted[-2]
project = "-".join(splitted[:-2])
for bug in self.get_bugs():
if bug.project.lower() == project.lower():
if len(splitted) < 3 or bug.bug_id.lower() == ("%s-%s" % (buggy, patched)):
return bug
return None
def checkout_and_compile(self, bug, working_dir, bug_pool_dir):
self.checkout(bug, working_dir)
self.compile(bug)
# level = self.compliance_level(bug)
File_util.rm_dir(bug_pool_dir)
shutil.copytree(working_dir, bug_pool_dir)
def _get_info_path(self):
return os.path.join(Config.DATASET_INFO_PATH, "bears")
def _get_dataset_path(self):
return os.path.join(Config.DATASET_PATH, "bears")
def get_bugs(self):
if self.bugs is not None:
return self.bugs
self.bugs = []
with open(os.path.join(self._get_info_path(), "bugs.json")) as f:
data = json.load(f)
for b in data:
(organization, project) = b["repository"]["url"].replace("https://github.com/", "").split("/")
self.bugs += [
Bug.Bug(self, "%s-%s" % (organization, project),
"%s-%s" % (b['builds']['buggyBuild']['id'], b['builds']['fixerBuild']['id']), b['diff'])
]
return self.bugs
def _get_project_info(self, bug):
try:
return bug.maven_info
except AttributeError:
pass
local_working_directory = bug.get_working_dir()
pom_path = bug.info['reproductionBuggyBuild']['projectRootPomPath']
buggy_build_id = bug.info['builds']['buggyBuild']['id']
pom_path = pom_path.partition(str(buggy_build_id))[2]
pom_path = pom_path.replace("/pom.xml", "")
pom_path = pom_path.replace("/", "", 1)
if pom_path:
local_working_directory = os.path.join(local_working_directory, pom_path)
cmd = f"""
export JAVA_HOME="{os.path.join(Config.JAVA8_HOME, '..')}";
export PATH="{Config.MAVEN_BIN}:$PATH";
cd {local_working_directory};
mvn com.github.tdurieux:project-config-maven-plugin:1.0-SNAPSHOT:info -q;
"""
info = json.loads(Cmd_util.run_cmd(cmd))
bug.maven_info = info
return info
def checkout_fixed(self, bug, fixed_dir):
if not fixed_folder.endswith(bug.name):
fixed_dir = os.path.join(fixed_dir, bug.name)
fixed_dir = self.set_bug_working_dir(bug, fixed_dir, True)
branch_id = "%s-%s" % (bug.project, bug.bug_id)
cmd = f"""
cd {self._get_dataset_path()};
git reset .;
git checkout -- .;
git clean -x -d --force;
git checkout -f master;
git checkout -f {branch_id}
"""
Cmd_util.run_cmd(cmd)
bears_info_path = os.path.join(self._get_dataset_path(), "bears.json")
with open(bears_info_path) as fd:
bug.info = json.load(fd)
cmd = f"""
cd {self._get_dataset_path() };
git log --format=format:%H --grep='Human patch from';
"""
fixed_commit = Cmd_util.run_cmd(cmd)
if len(fixed_commit) == 0:
raise Exception("fixed_commit should not be null.")
cmd = f"""cd {self._get_dataset_path()};
git checkout -f {fixed_commit};
cp -r . {bug.get_working_dir()}"""
Cmd_util.run_cmd(cmd)
return fixed_dir
def checkout(self, bug, output_dir):
self.set_bug_working_dir(bug, output_dir)
# branch id: u'webfirmframework-wff-453188520-453188718'
branch_id = "%s-%s" % (bug.project, bug.bug_id)
cmd = f"""
cd {self._get_dataset_path()};
git reset .;
git checkout -- .;
git clean -x -d --force;
git checkout -f master;
git checkout -f {branch_id}
"""
Cmd_util.run_cmd(cmd)
# '/mnt/recursive-repairthemall/RepairThemAll/script/../benchmarks/bears/bears.json'
# at this time, the benchmarks/bears/ are changed into bug-id version
bears_info_path = os.path.join(self._get_dataset_path(), "bears.json")
with open(bears_info_path) as fd:
bug.info = json.load(fd)
cmd = f"""
cd {self._get_dataset_path() };
git log --format=format:%H --grep='Changes in the tests';
"""
bug_commit = Cmd_util.run_cmd(cmd)
if len(bug_commit) == 0:
# if no bug_commit, then choose bug commit ID with message containing "Bug commit"
cmd = f"cd {self._get_dataset_path()}; git log --format=format:%H --grep='Bug commit'"
bug_commit = Cmd_util.run_cmd(cmd)
# u'cd /mnt/recursive-repairthemall/RepairThemAll/script/../benchmarks/bears;
# \ngit checkout -f b0706752ab6c822c6ab9758eb6cae8923b49e700;
# \ncp -r . /mnt/workingDir/Bears_webfirmframework-wff_453188520-453188718'
cmd = f"""cd {self._get_dataset_path()};
git checkout -f {bug_commit};
cp -r . {bug.get_working_dir()}"""
Cmd_util.run_cmd(cmd)
def compile(self, bug, early_exit=False):
"""
early_exit is for checkout.py
"""
# deal with bears compilation errors
compiler_error_bugs = File_util.read_file_to_list_strip(
os.path.join(Config.PROJECT_PATH, 'bugs_info', Config.bears_with_compile_error_path))
if f'{bug.project}-{bug.bug_id}' in compiler_error_bugs:
pom_file_path = os.path.join(bug.get_working_dir(), 'pom.xml')
assert os.path.exists(pom_file_path)
Bears_util.change_pom_for_compilation_error_bug(pom_file_path)
if early_exit:
return
local_working_directory = bug.get_working_dir()
pom_path = bug.info['reproductionBuggyBuild']['projectRootPomPath'] #./workspace/INRIA/spoon/211342085/pom.xml
buggy_build_id = bug.info['builds']['buggyBuild']['id'] # 211342085
pom_path = pom_path.partition(str(buggy_build_id))[2]
pom_path = pom_path.replace("/pom.xml", "")
pom_path = pom_path.replace("/", "", 1) #u'wffweb
if pom_path:
local_working_directory = os.path.join(
local_working_directory, pom_path
) # obtain pom.xml path. u'/mnt/workingDir/Bears_webfirmframework-wff_453188520-453188718/wffweb'
# export _JAVA_OPTIONS=-Djdk.net.URLClassPath.disableClassPathURLCheck=true;
cmd = f"""
cd {local_working_directory};
export JAVA_HOME={os.path.join(Config.JAVA8_HOME, '..') };
export PATH="{Config.MAVEN_BIN}:$PATH";
export _JAVA_OPTIONS=-Djdk.net.URLClassPath.disableClassPathURLCheck=true;
mvn -Dhttps.protocols=TLSv1.2 install -V -B -DskipTests -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -DskipITs=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -l.npm=true -Dskip.gulp=true -Dskip.bower=true;
mvn -Dhttps.protocols=TLSv1.2 test -DskipTests -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -DskipITs=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Denforcer.skip=true;
mvn dependency:build-classpath -Dmdep.outputFile="classpath.info";
"""
output = Cmd_util.run_cmd(cmd)
if "\n[INFO] BUILD FAILURE\n" in output:
fail_to_compile_path = os.path.join(local_working_directory, "..", 'fail_to_compile.tag')
Config.fail_to_compile = True
logger.error("the bug fails to compile!")
File_util.write_str_to_file(fail_to_compile_path, output, False)
File_util.write_line_to_file(Config.log_file, output)
def run_test(self, bug):
local_working_directory = bug.get_working_dir()
pom_path = bug.info['reproductionBuggyBuild']['projectRootPomPath']
buggy_build_id = bug.info['builds']['buggyBuild']['id']
pom_path = pom_path.partition(str(buggy_build_id))[2]
pom_path = pom_path.replace("/pom.xml", "")
pom_path = pom_path.replace("/", "", 1)
if pom_path:
local_working_directory = os.path.join(local_working_directory, pom_path)
cmd = f"""cd {local_working_directory};
export JAVA_HOME={os.path.join(Config.JAVA8_HOME, '..') };
export PATH="{Config.MAVEN_BIN}:$PATH";
export _JAVA_OPTIONS=-Djdk.net.URLClassPath.disableClassPathURLCheck=true;
rm -rf .git; git init; git commit -m 'init' --allow-empty;
mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -DskipITs=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Djacoco.skip=true -Denforcer.skip=true;
"""
return Cmd_util.run_cmd(cmd)
def failing_tests(self, bug):
tests = []
with open(os.path.join(self._get_info_path(), "bugs.json")) as fd:
data = json.load(fd)
for b in data:
(organization, project) = b["repository"]["url"].replace("https://github.com/", "").split("/")
project_id = "%s-%s" % (organization, project)
bug_id = "%s-%s" % (b['builds']['buggyBuild']['id'], b['builds']['fixerBuild']['id'])
if bug.project.lower() == project_id.lower() and bug.bug_id.lower() == bug_id.lower():
for t in b['tests']['failingClasses']:
tests += [t['testClass']]
return tests
return tests
def failing_test_methods(self, bug):
tests = []
with open(os.path.join(self._get_info_path(), "bugs.json")) as fd:
data = json.load(fd)
for b in data:
(organization, project) = b["repository"]["url"].replace("https://github.com/", "").split("/")
project_id = "%s-%s" % (organization, project)
bug_id = "%s-%s" % (b['builds']['buggyBuild']['id'], b['builds']['fixerBuild']['id'])
if bug.project.lower() == project_id.lower() and bug.bug_id.lower() == bug_id.lower():
for t in b['tests']['failureDetails']:
tests += [t['testClass'] + "#" + t['testMethod']]
return tests
return tests
def failing_test_methods_2(self, bug):
"""
for dynmoath patch explorer
"""
tests = []
with open(os.path.join(self._get_info_path(), "bugs.json")) as fd:
data = json.load(fd)
for b in data:
(organization, project) = b["repository"]["url"].replace("https://github.com/", "").split("/")
project_id = "%s-%s" % (organization, project)
bug_id = "%s-%s" % (b['builds']['buggyBuild']['id'], b['builds']['fixerBuild']['id'])
if bug.project.lower() == project_id.lower() and bug.bug_id.lower() == bug_id.lower():
for t in b['tests']['failureDetails']:
tests += [f"{t['testMethod']}({t['testClass']})"]
return tests
return tests
def failing_module(self, bug):
failing_module = bug.info['tests']['failingModule']
buggy_build_id = str(bug.info['builds']['buggyBuild']['id'])
try:
index_build = failing_module.index(buggy_build_id + "/")
return failing_module[index_build + len(buggy_build_id) + 1:]
except ValueError:
return "root"
def source_folders(self, bug):
folders = []
info = self._get_project_info(bug)
failing_module = self.failing_module(bug)
for module in info['modules']:
# when this assert fails, you might open your workspace at via a symbol link.
# this has been fixed by changing config PROJECT_PATH and PARENT_PROJECT_PATH
assert bug.get_working_dir() in module[
'baseDir'], f"bug.get_working_dir(): {bug.get_working_dir()} is not in in module['baseDir']: {module['baseDir']}"
module_name = module['baseDir'].replace(bug.get_working_dir() + '/', '')
if failing_module == module_name or failing_module == module['name']:
return self.abs_to_rel(bug.get_working_dir(), module['sources'])
return folders
def test_folders(self, bug):
folders = []
info = self._get_project_info(bug)
failing_module = self.failing_module(bug)
for module in info['modules']:
module_name = os.path.basename(module['baseDir'])
if failing_module == module_name or failing_module == module['name']:
return self.abs_to_rel(bug.get_working_dir(), module['tests'])
return folders
def bin_folders(self, bug):
info = self._get_project_info(bug)
failing_module = self.failing_module(bug)
for module in info['modules']:
module_name = os.path.basename(module['baseDir'])
if failing_module == module_name or failing_module == module['name']:
return self.abs_to_rel(bug.get_working_dir(), module['binSources'])
return []
def test_bin_folders(self, bug):
info = self._get_project_info(bug)
failing_module = self.failing_module(bug)
for module in info['modules']:
module_name = os.path.basename(module['baseDir'])
if failing_module == module_name or failing_module == module['name']:
return self.abs_to_rel(bug.get_working_dir(), module['binTests'])
return []
def classpath(self, bug):
info = self._get_project_info(bug)
failing_module = self.failing_module(bug)
deps = []
for module in info['modules']:
module_name = os.path.basename(module['baseDir'])
if failing_module != module_name and failing_module != module['name']:
deps += module['binSources']
deps += info['classpath']
# return ":".join(deps)
# add junit jar
classpath = ":".join(deps)
if "/junit-4" not in classpath and "/junit-3" not in classpath:
deps += [os.path.join(Config.M2_REPO, "junit", "junit", "4.12", "junit-4.12.jar")]
deps += [os.path.join(Config.M2_REPO, "org", "hamcrest", "hamcrest-core", "1.3", "hamcrest-core-1.3.jar")]
classpath = ":".join(deps)
# add bin folders of src and tests of the buggy program
bin_folders = self.bin_folders(bug)
test_folders = self.test_bin_folders(bug)
if len(bin_folders) > 0 and len(test_folders) > 0:
bin_folder_path = os.path.join(bug.get_working_dir(), bin_folders[0])
test_bin_folder_path = os.path.join(bug.get_working_dir(), test_folders[0])
if bin_folder_path not in classpath:
classpath = bin_folder_path + ":" + classpath
if test_bin_folder_path not in classpath:
classpath = test_bin_folder_path + ":" + classpath
return classpath
def compliance_level(self, bug):
info = self._get_project_info(bug)
return info['complianceLevel']
def abs_to_rel(self, root, folders):
if root[-1] != '/':
root += "/"
output = []
for folder in folders:
output.append(folder.replace(root, ""))
return output
def get_patch_diff(self, bug):
source_folders = self.source_folders(bug)
assert len(source_folders) == 1
buggy_dir = os.path.join(bug.get_working_dir(), source_folders[0])
from copy import deepcopy
bugCopy = deepcopy(bug)
fixed_dir = Config.TMP_FIXED_DIR
fixed_dir = self.checkout_fixed(bugCopy, fixed_dir)
fixed_dir = os.path.join(fixed_dir, source_folders[0])
patch_diff = Cmd_util.run_cmd(f"diff -Naur {buggy_dir} {fixed_dir}")
# must delete to save / space!
logger.info(f"rm tmp_checkout_dir: {fixed_dir}")
File_util.rm_dir_safe_contain(fixed_dir, '/tmp/')
return patch_diff-
Add the fault localizer directory into the
fl_modulesfolder. -
Modify the
APRConfig/localization/Localizer_factory.pyas highlighted in the following comments:
# -*- encoding: utf-8 -*-
'''
@Description:
@Date : 2021/08/14 21:59:03
@Author : apr
'''
import logging
import Gzoltar
import Other_localizer # 1. add import
logger = logging.getLogger()
class LocalizerFactory():
def create_localizer(self, localizer_name, fl_args):
if localizer_name.lower() == "gzoltar_v0.1.1" or localizer_name.lower() == "gzoltar":
return Gzoltar.Gzoltar(fl_args)
elif dataset_name.lower() == "other_localizer": # 2. add an else if stmt
return Other_localizer.Other_localizer(fl_args)
else:
raise Exception(f"unknown localizer name: {localizer_name}")- Add a new file (e.g.,
GZoltar.pyfor the GZoltar fault localizer) into theAPRConfig/localization/folder. The content is:
import os
import Localizer
import Config
from utils import Cmd_util
class Gzoltar(Localizer.Localizer):
def __init__(self, fl_args):
super().__init__(fl_args)
"""
gzoltar 0.1.1 version
"""
self.jarPath = Config.GZOLTAR_JAR_PATH
self.jarJavaVersion = Config.JAVA8_HOME
self.mainClass = "apr.module.fl.main.Main"
def localize(self):
cmd = f"""
cd {self.workingDir};
export JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8 -Duser.language=en-US -Duser.country=US -Duser.language=en";
TZ="America/New_York"; export TZ;
export PATH="{self.jarJavaVersion}:$PATH";
export JAVA_HOME="{self.jarJavaVersion}";
timeout 20m java {Config.JAVA_ARGS} -cp {self.jarPath} {self.mainClass} \\
--externalProjPath {self.externalProjPath} \\
--srcJavaDir {self.srcJavaDir} \\
--binJavaDir {self.binJavaDir} \\
--binTestDir {self.binTestDir} \\
--jvmPath {self.jvmPath} \\
--failedTests {self.failedTests} \\
--dependencies {self.dependencies} \\
--outputDir {self.outputDir} \\
--workingDir {self.workingDir};
"""
self.run_fl_cmd(cmd)
pass-
Add the patch generation algorithm directory into the
apr_toolsfolder. -
Modify the
APRConfig/apr/Apr_factory.pyas highlighted in the following comments:
# -*- encoding: utf-8 -*-
'''
@Description:
@Date : 2021/08/14 21:59:03
@Author : apr
'''
import logging
import Simfix
import Nopol
import Tbar # 1. add import
logger = logging.getLogger()
class AprFactory():
def __init__(self) -> None:
self.name = "apr"
pass
def create_apr(self, apr_name, apr_args):
if apr_name.lower().startswith("simfix_"):
return Simfix.Simfix(apr_args, apr_name.lower().split("_")[-1])
elif apr_name.lower().startswith("nopol_"):
return Nopol.Nopol(apr_args, "nopol", apr_name.lower().split("_")[-1])
elif apr_name.lower().startswith("dynamoth_"):
return Nopol.Nopol(apr_args, "dynamoth", apr_name.lower().split("_")[-1])
elif apr_name.lower().startswith("tbar_"): # 2. add an else if stmt
return Tbar.Tbar(apr_args, apr_name.lower().split("_")[-1])
else:
raise Exception(f"unknown {self.name} name: {apr_name}")- Add a new file (e.g.,
TBar.pyfor the TBar patch generation algorithm) into theAPRConfig/apr/folder. The content is:
import os
import Apr
import Config
class Tbar(Apr.Apr):
def __init__(self, args, validate_mode):
super().__init__(args)
self.onlyRunDefault = "false"
if validate_mode == "1":
self.onlyRunDefault = "true"
"""
tbar
release date: July 2019 (from acm)
commit id: 35f33ec9cd5ad085deaf68c9ff4cd2653f6f7280
"""
self.jarPath = os.path.join(
Config.PARENT_PROJECT_PATH, "apr_tools",
"TBar/versions/TBar-0.0.1-SNAPSHOT-jar-with-dependencies.jar")
self.jarJavaVersion = Config.JAVA7_HOME
self.mainClass = "edu.lu.uni.serval.tbar.main.Main"
def repair(self):
# timeout 360m java {Config.JAVA_ARGS} -cp {self.jarPath} {self.mainClass} \\
cmd = f"""
cd {self.workingDir};
export JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8 -Duser.language=en-US -Duser.country=US -Duser.language=en";
TZ="America/New_York"; export TZ;
export PATH="{self.jarJavaVersion}:$PATH";
export JAVA_HOME="{self.jarJavaVersion}";
timeout 360m java {Config.JAVA_ARGS} -cp {self.jarPath} {self.mainClass} \\
--flDir {self.fl_dir} \\
--flTimeCost {self.fl_time_cost} \\
--outputDir {self.outputDir} \\
--jvmPath {self.jvmPath} \\
--externalProjPath {self.externalProjPath} \\
--binJavaDir {self.binJavaDir} \\
--binTestDir {self.binTestDir} \\
--srcJavaDir {self.srcJavaDir} \\
--failedTests {self.failedTests} \\
--dependencies {self.dependencies} \\
--onlyRunDefault {self.onlyRunDefault}
"""
self.run_repair_cmd(cmd)
pass-
Add the patch validator directory into the
patch_validatorfolder. -
Modify the
APRConfig/Config.pyas highlighted in the following comment:
import os
rerun_dataset = True
# MAX_INT
MAX_INT = 1000000000000
PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
PARENT_PROJECT_PATH = os.path.join(os.path.realpath(os.path.dirname(__file__)), "..")
M2_REPO = os.path.expanduser("~/.m2/repository")
MAVEN_BIN = os.environ.get("MAVEN_BIN", "/usr/bin/mvn")
DATASET_PATH = os.path.abspath(os.path.join(PROJECT_PATH, "..", "datasets"))
DATASET_INFO_PATH = os.path.abspath(os.path.join(PROJECT_PATH, "..", "datasets", "dataset_info"))
assert os.path.exists(DATASET_PATH)
assert os.path.exists(DATASET_INFO_PATH)
JAVA7_HOME = os.path.expanduser("~/env/jdk1.7.0_80/bin/")
JAVA8_HOME = os.path.expanduser("~/env/jdk1.8.0_202/bin/")
JAVA11_HOME = os.path.expanduser("~/env/jdk-11/bin/")
JAVA_ARGS = "-Xmx4g -Xms1g"
assert os.path.exists(JAVA7_HOME)
assert os.path.exists(JAVA8_HOME)
TIMEOUT = 120
TMP_BUGGY_DIR = "/tmp/buggy"
TMP_FIXED_DIR = "/tmp/fixed"
TMP_OUTPUT_DIR = os.path.join(PARENT_PROJECT_PATH, "results")
LOG_NAME = "execution_framework.log"
GZOLTAR_JAR_PATH = os.path.join(
PARENT_PROJECT_PATH,
"fl_modules/fault_localizer/versions/gzoltar_localizer-0.0.1-SNAPSHOT-jar-with-dependencies.jar")
# specifiy the runnable jar file path of the patch validator
EXTERNAL_VALIDATOR = os.path.join(
PARENT_PROJECT_PATH, "patch_validator/patch_validator/versions/PatchTest-0.0.1-SNAPSHOT-jar-with-dependencies.jar")We would like to sincerely thank Thomas Durieux, Fernanda Madeiral, Matias Martinez, and Rui Abreu for contributing a great framework (i.e., RepairThemAll), which serves as a quite useful reference point for how to construct an executable framework. We also adopted some software design patterns used in RepairThemAll, and finally accomplished a new executable framework that properly decouples the APR implementation into three sub-modules, including fault localization, patch generation and patch validation, to support the bias mitigation and validation as well as further explorations on APR evaluation for potential end users.