Skip to content
Open
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
90 changes: 58 additions & 32 deletions SofaRegressionProgram/SofaRegressionProgram.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,33 @@
import Sofa
import SofaRuntime # importing SofaRuntime will add the py3 loader to the scene loaders
import tools.RegressionSceneList as RegressionSceneList
from tools import ProgressBarHandler as pbh
import tools.RegressionWorker as RegressionWorker

regression_file_extension = ".regression-tests"

class RegressionProgram:
def __init__(self, input_folder, filter = None, disable_progress_bar = False, verbose = False):
def __init__(self, input_folder, filter = None, disable_progress_bar = False, verbose = False, nbr_jobs = 1):
"""Initialize the RegressionProgram

Args:
input_folder (str): Path to the folder containing regression test files.
filter (str): Regex pattern to filter scene files (e.g., '^demo.*.scn$'). If None, no filter is applied. Defaults to None.
disable_progress_bar (bool, optional): If True, disable progress bars. Defaults to False.
verbose (bool, optional): If True, enable verbose output. Defaults to False.
nbr_jobs (int, optional): Number of scenes to write/compare at the same time. 0 means one per logical core. Defaults to 1.
"""
self.scene_sets = [] # List <RegressionSceneList>
self.disable_progress_bar = disable_progress_bar
self.verbose = verbose
self.legacy_mode = False
self.nbr_jobs = RegressionWorker.resolve_nbr_jobs(nbr_jobs)

for root, dirs, files in os.walk(input_folder):
for file in files:
if file.endswith(regression_file_extension):
file_path = os.path.join(root, file)

scene_list = RegressionSceneList.RegressionSceneList(file_path, filter, self.disable_progress_bar, verbose)
scene_list = RegressionSceneList.RegressionSceneList(file_path, filter, self.disable_progress_bar, verbose, self.nbr_jobs)

scene_list.process_file()
self.scene_sets.append(scene_list)
Expand All @@ -48,30 +50,42 @@ def nbr_error_in_sets(self):
nbr_errors = nbr_errors + scene_list.get_nbr_errors()
return nbr_errors

def nbr_parsing_error_in_sets(self):
nbr_errors = 0
for scene_list in self.scene_sets:
nbr_errors = nbr_errors + scene_list.get_nbr_parsing_errors()
return nbr_errors

def log_errors_in_sets(self):
for scene_list in self.scene_sets:
scene_list.log_scenes_errors()

def run_all_sets(self, mode, description):
"""Run every scene of every set in `mode` ("write" or "compare").

When several jobs are allowed, the scenes of all the sets are scheduled
in a single pool: a set holding fewer scenes than the number of jobs
would otherwise leave most of the workers idle.
"""
tasks = []
for scene_list in self.scene_sets:
scene_list.legacy_mode = self.legacy_mode
tasks.extend(scene_list.build_tasks(mode))

return RegressionWorker.run_scene_tasks(
tasks,
nbr_jobs=self.nbr_jobs,
on_result=lambda task, result: task["scene_list"].apply_result(task, result),
description=description,
disable_progress_bar=self.disable_progress_bar)

def write_sets_references(self, id_set=0):
scene_list = self.scene_sets[id_set]
nbr_scenes = scene_list.write_all_references()
return nbr_scenes

def write_all_sets_references(self):
nbr_sets = len(self.scene_sets)

pbar_sets = pbh.ProgressBarHandler(total=nbr_sets, disable=self.disable_progress_bar)
pbar_sets.set_description("Write All sets")

nbr_scenes = 0
for i in range(0, nbr_sets):
nbr_scenes = nbr_scenes + self.write_sets_references(i)
pbar_sets.update(1)

if not self.disable_progress_bar:
pbar_sets.close()

return nbr_scenes
return self.run_all_sets("write", "Write All sets")

def compare_sets_references(self, id_set=0):
scene_list = self.scene_sets[id_set]
Expand All @@ -80,18 +94,7 @@ def compare_sets_references(self, id_set=0):
return nbr_scenes

def compare_all_sets_references(self):
nbr_sets = len(self.scene_sets)
pbar_sets = pbh.ProgressBarHandler(total=nbr_sets, disable=self.disable_progress_bar)
pbar_sets.set_description("Compare All sets")

nbr_scenes = 0
for i in range(0, nbr_sets):
nbr_scenes = nbr_scenes + self.compare_sets_references(i)
pbar_sets.update(1)

pbar_sets.close()

return nbr_scenes
return self.run_all_sets("compare", "Compare All sets")

def replay_references(self, id_scene, id_set=0):
scene_list = self.scene_sets[id_set]
Expand Down Expand Up @@ -122,7 +125,15 @@ def make_parser():
help="A regex filter to select scenes to test (e.g., '^demo.*.scn$')",
type=str)

parser.add_argument('--replay',
parser.add_argument('-j', '--jobs',
dest='jobs',
help="Number of scenes to process at the same time (each one still runs in its own\n"
"isolated process, so the results are unchanged). 0 means one job per logical\n"
"core. Default: 1 (sequential).",
type=int,
default=1)

parser.add_argument('--replay',
dest='replay',
help=f"Will launch runSofa on the scene number X (input number) in the input the list of the {regression_file_extension} file given as input and display the scene references aside from the simulation",
type=int)
Expand Down Expand Up @@ -163,6 +174,8 @@ def make_parser():
python SofaRegressionProgram.py --input ./scenes
python SofaRegressionProgram.py --input ./scenes --filter \"$demo.*.scn\"
python SofaRegressionProgram.py --input ./scenes --replay 5
python SofaRegressionProgram.py --input ./scenes --jobs 8
python SofaRegressionProgram.py --input ./scenes --write-references -j 0
'''

return parser
Expand All @@ -175,7 +188,7 @@ def make_parser():

# 2- Process file
if args.input is not None:
reg_prog = RegressionProgram(args.input, args.filter, args.progress_bar_is_disabled, args.verbose)
reg_prog = RegressionProgram(args.input, args.filter, args.progress_bar_is_disabled, args.verbose, args.jobs)
else:
parser.print_help()
exit("Error: Argument is required ! Quitting.")
Expand All @@ -185,7 +198,11 @@ def make_parser():
if args.legacy_mode:
print("Legacy regression mode activated.")
reg_prog.legacy_mode = True


if reg_prog.nbr_jobs > 1:
print(f"Processing up to {reg_prog.nbr_jobs} scenes at the same time.")


if args.replay is not None:
replayId = int(args.replay)
reg_prog.replay_references(replayId)
Expand All @@ -212,14 +229,23 @@ def make_parser():

np.set_printoptions(legacy='1.25') # revert printing floating-point type in numpy (concretely remove np.array when displaying a list of np.float)

nbr_parsing_errors = reg_prog.nbr_parsing_error_in_sets()

print ("### Number of sets Done: " + str(len(reg_prog.scene_sets)))
print ("### Number of scenes Done: " + str(nbr_scenes))
if nbr_parsing_errors > 0:
# Those scenes have not been processed at all: report them as an error
# so that an invalid list file cannot silently reduce the test coverage.
print ("### Number of invalid lines skipped: " + str(nbr_parsing_errors))
if args.write_mode is False:
print ("### Number of scenes failed: " + str(reg_prog.nbr_error_in_sets()))
reg_prog.log_errors_in_sets()
if reg_prog.nbr_error_in_sets() > 0:
sys.exit(1) # exit with error(s)

if nbr_parsing_errors > 0:
sys.exit(1) # exit with error(s)

sys.exit(0) # exit without error


10 changes: 10 additions & 0 deletions SofaRegressionProgram/tools/RegressionSceneData.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ def log_errors(self):
else:
helper.writeSuccess(f"{self.file_scene_path} | Number of key frames compared: {self.nbr_tested_frame} | run time: {self.total_run_time/1e9} seconds. ")

def apply_worker_result(self, result):
"""Copy the fields reported by an isolated worker process back onto this
object so that log_errors() and error counting behave as if the scene had
been compared in-process."""
self.regression_failed = bool(result.get("regression_failed", False))
self.nbr_tested_frame = int(result.get("nbr_tested_frame", 0))
self.total_run_time = result.get("total_run_time", 0)
self.error_by_dof = result.get("error_by_dof", [])
self.total_error = result.get("total_error", [])

def print_meca_objs(self):
helper.writeLog("# Nbr Meca: " + str(len(self.meca_objs)))
counter = 0
Expand Down
Loading