Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
eae9298
Addition of solver voting verifiers and two mutators
jossehey Mar 30, 2025
7b0493b
Added solution limit for gurobi and some minor changes for new solver…
jossehey Apr 8, 2025
e5c030c
Changed solver argument to take 1 or more arguments
jossehey Apr 8, 2025
23ede89
addition and bugfixing of new mutators
jossehey Apr 8, 2025
e807867
fixed a bug when adding lexical ordering constraints that allowed non…
jossehey Apr 8, 2025
77c5767
added printing of domains when rerunning on certain verifiers (still …
jossehey Apr 8, 2025
f3e2c78
fixed bug in expression mutator where gcc was allowing non-int variab…
jossehey Apr 8, 2025
0d6e8cb
Found bug regarding AllDifferentExcept0 (not fixed, added TODO)
jossehey Apr 8, 2025
9818c64
reimplementation of eplace_at_path to fix infinite recursion bug and…
jossehey Apr 9, 2025
18a16f6
changed the runner to choose verifiers based on the amount of given s…
jossehey Apr 11, 2025
24184c1
type_aware_operator_replacement now also checks whether the expressio…
jossehey Apr 11, 2025
a624d3f
added 20% chance to perform generation type mutation
jossehey Apr 13, 2025
1c44e8e
some bug fixes when generating new functions
jossehey Apr 13, 2025
3da3a5f
bugfixes in generation of new expressions
jossehey Apr 16, 2025
6227201
Addition of fuctions to classify bugs while testing (hardcoded)
jossehey Apr 16, 2025
57b4085
Removed check before mutations to increase testing performance
jossehey Apr 16, 2025
4b7cf89
Added logic to classify bugs while testing
jossehey Apr 16, 2025
1fb4456
Changed major bug in choosing model file where the choice was made us…
jossehey Apr 17, 2025
a4811a5
Addition of new verifier and accompanying mutator
jossehey Apr 17, 2025
c6a7fb7
Some refactoring of the old verifiers
jossehey Apr 17, 2025
da8193f
Merge branch 'formula-strengthening-weakening' into model-counting
jossehey Apr 17, 2025
b3c29a0
added some exception raising, added some bug classification, fixed pa…
jossehey Apr 17, 2025
7b60431
Change to InDomain to make first arg have only variables and second a…
jossehey Apr 18, 2025
801e858
Removed is_rerun argument
jossehey Apr 18, 2025
fe10405
added variables to output dict and removed is_rerun variable to print…
jossehey Apr 18, 2025
747c005
some minor additions to classification of bugs + removal of GEN/MUT s…
jossehey Apr 18, 2025
7ef2037
added variables (along with their bounds) to the error dictionaries
jossehey Apr 19, 2025
a4aef0f
fixed bug where expression wasn't found when in NDVarArray
jossehey Apr 23, 2025
76c6913
added function writing errors to csv + addition of some new error cla…
jossehey Apr 23, 2025
e103a55
Minor change to account for change in seed implementation for some ve…
jossehey Apr 23, 2025
d5b06b2
changes to make final results possible
jossehey Apr 29, 2025
2af99c7
bugfix with table constraints
jossehey Apr 29, 2025
5620574
Addition of small new mutation type: replacing domain of a variable t…
jossehey Apr 29, 2025
cf19736
small bug fix in case there are no valid mutators (unlikely)
jossehey Apr 29, 2025
58ebc1f
fixed bug where parity check always checked two arguments exactly
jossehey May 3, 2025
4147ef2
Added rerun to find where the bug occurred when bug happened during g…
jossehey May 3, 2025
f208052
changes to subtrees again after merge conflict
jossehey May 3, 2025
56389b4
Small adjustment to mutator probabilities
jossehey May 5, 2025
0947f54
Added some global constraints to the weakening and strengthening muta…
jossehey May 5, 2025
6a732ed
Some small refactorings
jossehey May 5, 2025
384702e
Added mm_mutation probability as parameter
jossehey May 18, 2025
032833b
Added new equivalence verifier for solver voting
jossehey May 18, 2025
33d883d
Added new verifier that checks single solution
jossehey May 18, 2025
7ef2dc2
refactorings and removal of the 'multiple' argument in Function class
jossehey May 18, 2025
b093dbc
Added a new superclass for all the solver voting verifiers, because a…
jossehey May 18, 2025
8a2a0d7
changed solve call before str/wkn to solve() instead of solveAll(). T…
jossehey May 20, 2025
fef14a4
Bugfix when looking at which mutator should be used
jossehey May 20, 2025
883efe6
changed domain mutator to take random subdomain or random larger doma…
jossehey May 20, 2025
31aa18f
bugfix to be compatible with the new domain changer
jossehey May 20, 2025
45fc235
Small bugfix
jossehey May 20, 2025
b9fcc43
fixed bug when solution_limit was reached + fixed printing of error
jossehey May 21, 2025
d3b7fa4
fixed bug for domain changer where the chosen domain range was empty
jossehey May 21, 2025
f3b9587
added last line of stacktrace to the output
jossehey May 21, 2025
8c6962a
Added tuple support because apparently those are part of the startmod…
jossehey May 21, 2025
7525b75
Element now cannot take a constant as index that is larger than the f…
jossehey May 21, 2025
39886af
bugfix when no solution limit
jossehey May 22, 2025
fcf3d2c
some more classifications
jossehey May 22, 2025
09a500e
bugfix when looking where bug happened
jossehey May 22, 2025
4a1a0e7
Removal of the exlude dict due to memory reasons for long runs (still…
jossehey May 24, 2025
3fbc608
All changes together
jossehey Oct 28, 2025
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
71 changes: 66 additions & 5 deletions fuzz_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import time
from pathlib import Path
from multiprocessing import Process,Lock, Manager, set_start_method,Pool, cpu_count
import csv
from datetime import datetime

import cpmpy as cp

Expand All @@ -26,14 +28,15 @@ def check_positive(value):
return ivalue

parser = argparse.ArgumentParser(description = "A python application to fuzz_test your solver(s)")
parser.add_argument("-s", "--solver", help = "The Solver to use", required = False,type=str,choices=available_solvers, default=available_solvers[0])
parser.add_argument("-s", "--solver", help = "The Solver to use", required = False,type=str,choices=available_solvers, nargs='+', default=[available_solvers[0]])
parser.add_argument("-m", "--models", help = "The path to load the models", required=False, type=str, default="models")
parser.add_argument("-o", "--output-dir", help = "The directory to store the output (will be created if it does not exist).", required=False, type=str, default="output")
parser.add_argument("-g", "--skip-global-constraints", help = "Skip the global constraints when testing", required=False, default = False)
parser.add_argument("--max-failed-tests", help = "The maximum amount of test that may fail before quitting the application (by default an infinite amount of tests can fail). if the maximum amount is reached it will quit even if the max-minutes wasn't reached", required=False, default=math.inf ,type=check_positive)
parser.add_argument("--max-minutes", help = "The maximum time (in minutes) the tests should run (by default the tests will run forever). The tests will quit sooner if max-bugs was set and reached or an keyboardinterrupt occured", required=False, default=math.inf ,type=check_positive)
parser.add_argument("-mpm","--mutations-per-model", help = "The amount of mutations that will be executed on every model", required=False, default=5 ,type=check_positive)
parser.add_argument("-p","--amount-of-processes", help = "The amount of processes that will be used to run the tests", required=False, default=cpu_count()-1 ,type=check_positive) # the -1 is for the main process
parser.add_argument("--mm-prob", help="The probability that a metamorphic mutation will be chosen in case of a verifier that allows other mutations", required=False, default=1, type=float)
args = parser.parse_args()
models = []
max_failed_tests = args.max_failed_tests
Expand All @@ -49,7 +52,7 @@ def check_positive(value):
os.makedirs(args.output_dir, exist_ok=True)

# showing the info about the given params to the user
print("\nUsing solver '"+args.solver+"' with models in '"+args.models+"' and writing to '"+args.output_dir+"'." ,flush=True,end="\n\n")
print("\nUsing solver(s): '" + ", ".join(args.solver)+"' with models in '"+args.models+"' and writing to '"+args.output_dir+"'." ,flush=True,end="\n\n")
print("Will use "+str(args.amount_of_processes)+ " parallel executions, starting...",flush=True,end="\n\n")

# creating the vars for the multiprocessing
Expand All @@ -65,9 +68,20 @@ def check_positive(value):

# creating processes to run all the tests
processes = []
process_args = (current_amount_of_tests, current_amount_of_error, lock, args.solver, args.mutations_per_model ,models ,max_failed_tests,args.output_dir, max_time)

# FOR EXPERIMENTS:
solvers = ['ortools', 'minizinc', 'choco', 'gurobi']
verifiers = ["solver_vote_count_verifier", "solver_vote_eq_verifier", "solver_vote_sat_verifier", "solver_vote_sol_verifier", "strengthening_weakening_verifier"]
solver_counts = {s: manager.Value(f"{s}", 0) for s in solvers}
verifier_counts = {v: manager.Value(f"{v}", 0) for v in verifiers}
verifier_run_times = {v: manager.Value(f"{v}", 0) for v in verifiers}
from itertools import combinations
solver_combos = [list(c) for c in combinations(solvers, 2)]

for x in range(args.amount_of_processes):
process_args = (current_amount_of_tests, current_amount_of_error, lock, solver_combos[x],
args.mutations_per_model, models, max_failed_tests, args.output_dir, max_time, args.mm_prob,
solver_counts, verifier_counts, verifier_run_times) # PAS TERUG AAN NA EXPERIMENTEN
processes.append(Process(target=run_verifiers,args=process_args))

try:
Expand All @@ -76,8 +90,14 @@ def check_positive(value):
process.start()

for process in processes:
process.join()
process.close()
process.join(timeout=args.max_minutes*60) # wait max double minutes

# If any process is still alive after timeout, terminate it
for process in processes:
if process.is_alive():
print(f"Forcefully terminating process {process.pid}", flush=True)
process.terminate()
process.join() # Clean up

except KeyboardInterrupt as e:
print("interrupting...",flush=True,end="\n")
Expand All @@ -96,4 +116,45 @@ def check_positive(value):
else:
print("Succesfully executed " +str(current_amount_of_tests.value) + " tests, "+str(current_amount_of_error.value)+" tests failed",flush=True,end="\n")

run_name = args.output_dir
minutes_ran = args.max_minutes
mpm = args.mutations_per_model
mm_prob = args.mm_prob
amnt_tests = current_amount_of_tests.value
amnt_errors = current_amount_of_error.value
solver_count_vals = {s: c.value for s, c in solver_counts.items()}
verifier_count_vals = {v: c.value for v, c in verifier_counts.items()}
verifier_runtime_vals = {v: t.value for v, t in verifier_run_times.items()}
print(f"Run \"{run_name}\" ran for {minutes_ran} minutes and executed {amnt_tests} tests of which {amnt_errors} failed.")
print("Amount of times each solver ran for:", solver_count_vals)
print("Amount of times each verifier ran:", verifier_count_vals)
print("Time each verifier ran:", verifier_runtime_vals)

# Prepare timestamp and filename
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
stats_filename = f"{run_name}_{timestamp}.csv"

# Create the rows for the CSV
headers = ["run_name", "mutations_per_model", "mm_probability", "minutes_ran", "amnt_tests", "amnt_errors"]
row = [run_name, mpm, mm_prob, minutes_ran, amnt_tests, amnt_errors]

# Optionally, flatten solver and verifier data into columns
for s, count in solver_count_vals.items():
headers.append(f"solver_count_{s}")
row.append(count)

for v, count in verifier_count_vals.items():
headers.append(f"verifier_count_{v}")
row.append(count)

for v, t in verifier_runtime_vals.items():
headers.append(f"verifier_runtime_{v}")
row.append(t)

# Save to CSV
with open(stats_filename, mode="w", newline="") as f:
writer = csv.writer(f)
writer.writerow(headers)
writer.writerow(row)

print(f"Saved run statistics to {stats_filename}")
5 changes: 4 additions & 1 deletion fuzz_test_rerunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ def rerun_file(failed_model_file,output_dir ):
error_data = pickle.loads(fpcl.read())
random.seed(error_data["seed"])
if error_data["error"]["type"].name != "fuzz_test_crash": # if it is a fuzz_test crash error we skip it
verifier_kwargs = {'solver': error_data["solver"], "mutations_per_model": error_data["mutations_per_model"], "exclude_dict": {}, "time_limit": time.time()*3600, "seed": error_data["seed"]}
solver = error_data["solver"]
verifier_kwargs = {'solver': solver, "mutations_per_model": error_data["mutations_per_model"], "exclude_dict": {}, "time_limit": time.time()*3600, "seed": error_data["seed"]}
if 'mm_prob' in error_data["error"]:
verifier_kwargs['mm_prob'] = error_data["error"]["mm_prob"]
error = lookup_verifier(error_data["verifier"])(**verifier_kwargs).rerun(error_data["error"])
error_data["error"] = error

Expand Down
Loading