""" test_xtuple_0001.py Simulates the xtuple-0001 defect: orQuery::orQuery() uses QStringList::contains() (O(M) linear scan) inside a while loop that iterates P times over SQL parameter placeholders. Total complexity: O(P * M) before fix, O(P) amortized after. Defect: OpenRPT/renderer/orutils.cpp missingParamList.contains() in param loop Fix: QSet shadow set for O(1) membership, keep QStringList for output """ import time import sys export_PYTHONUNBUFFERED = True # noqa: always unbuffered def parse_params_defective(param_names): """ Simulate the defective pattern: for each param occurrence, check if already in a list (O(M) scan). param_names: list of param name strings (with repeats, simulating P occurrences). Returns the deduplicated missing_param_list. """ missing_param_list = [] for n in param_names: # QStringList::contains is O(M) linear scan if n not in missing_param_list: missing_param_list.append(n) return missing_param_list def parse_params_fixed(param_names): """ Simulate the fixed pattern: use a set for O(1) membership, append to list for ordered output. """ missing_param_set = set() missing_param_list = [] for n in param_names: if n not in missing_param_set: missing_param_set.add(n) missing_param_list.append(n) return missing_param_list def build_workload(P, M): """ Build a workload of P parameter placeholder occurrences, drawn from M distinct missing param names (all missing = worst case). """ import random random.seed(42) names = [f"param_{i}" for i in range(M)] # repeat names across P occurrences return [names[i % M] for i in range(P)] def benchmark(fn, param_names, label, reps=5): # warm up fn(param_names) best = float("inf") for _ in range(reps): t0 = time.perf_counter() result = fn(param_names) t1 = time.perf_counter() best = min(best, t1 - t0) return best, result def run_test(P, M, min_speedup=3.0): print(f"\n--- P={P} param occurrences, M={M} distinct missing params ---") params = build_workload(P, M) t_defective, r_defective = benchmark(parse_params_defective, params, "defective") t_fixed, r_fixed = benchmark(parse_params_fixed, params, "fixed") # Results must be identical (same dedup, same order) assert r_defective == r_fixed, ( f"FAIL: result mismatch\n defective={r_defective[:5]}...\n fixed={r_fixed[:5]}..." ) speedup = t_defective / t_fixed if t_fixed > 0 else float("inf") print(f" defective: {t_defective*1000:.3f} ms") print(f" fixed: {t_fixed*1000:.3f} ms") print(f" speedup: {speedup:.1f}x") if speedup >= min_speedup: print(f" PASS (speedup {speedup:.1f}x >= {min_speedup}x)") else: print(f" FAIL (speedup {speedup:.1f}x < {min_speedup}x)") return False return True if __name__ == "__main__": all_pass = True # Small case - may not show speedup (overhead dominates) # Just verify correctness params_small = build_workload(100, 50) r_d = parse_params_defective(params_small) r_f = parse_params_fixed(params_small) assert r_d == r_f, "FAIL: small case result mismatch" print("N=100,M=50: correctness OK") # Medium case ok = run_test(P=1000, M=500, min_speedup=3.0) all_pass = all_pass and ok # Large case ok = run_test(P=5000, M=2000, min_speedup=5.0) all_pass = all_pass and ok print() if all_pass: print("ALL TESTS PASSED") sys.exit(0) else: print("SOME TESTS FAILED") sys.exit(1)