#!/usr/bin/env python3 """QuixBugs'tan bağımsız (üçüncü kişilerce yazılmış) bir veri seti bölümü oluşturur. QuixBugs (Lin ve ark., 2017; MIT lisansı) Quixey Challenge'ın 40 klasik algoritmasını Python ve Java'da, her biri tek satırlık bir hatayla sunar. Bu betik: * doğru sürümü test edilecek kod olarak alır (Python açıklama dizgisi Java'ya Javadoc olarak eklenir), * doğru ↔ hatalı sürüm farkından "yerleştirilmiş hata" yamasını otomatik çıkarır, * QuixBugs'ın kendi test vakalarını referans paket olarak dönüştürür (pytest / JUnit 4 ve 5), * her öğeyi doğrular (referans doğru kodda tamamen yeşil, hatalı kodda en az bir test başarısız), * aynı algoritma iki kez sayılmasın diye farklı programları Python ve Java arasında paylaştırır. git clone --depth 1 https://github.com/jkoppel/QuixBugs /tmp/QuixBugs python scripts/import_quixbugs.py /tmp/QuixBugs --per-language 10 --seed 2026 """ from __future__ import annotations import argparse import difflib import json import random import re import shutil import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "backend")) from app.sandbox.registry import get_runner # noqa: E402 OUT = ROOT / "benchmarks" / "quixbugs" SOURCE = {"dataset": "QuixBugs", "url": "https://github.com/jkoppel/QuixBugs", "license": "MIT", "citation": "D. Lin, J. Koppel, A. Chen, A. Solar-Lezama, SPLASH Companion 2017"} DOC_RE = re.compile(r'\n\s*"""(.*?)"""\s*$', re.S) def docstring(buggy_py: str) -> tuple[str, str]: """QuixBugs Python dosyalarında açıklama dizgisi kodun sonundadır: (kod, açıklama).""" m = DOC_RE.search(buggy_py) return (buggy_py[:m.start()].rstrip() + "\n", m.group(1).strip()) if m else (buggy_py, "") def one_hunk(correct: str, buggy: str) -> tuple[str, str] | None: """Doğru → hatalı tek bir değişiklik bloğu varsa (from, to) döndürür; boşluk farkları yok sayılır.""" a, b = correct.splitlines(), buggy.splitlines() sm = difflib.SequenceMatcher(None, [x.strip() for x in a], [x.strip() for x in b], autojunk=False) # yalnızca boş satırlardan oluşan farklar (ör. dosya başındaki fazladan boş satır) yok sayılır hunks = [op for op in sm.get_opcodes() if op[0] != "equal" and any(x.strip() for x in a[op[1]:op[2]] + b[op[3]:op[4]])] if len(hunks) != 1: return None _, i1, i2, j1, j2 = hunks[0] if i1 == i2: # hatalı sürümde eklenmiş satır: bağlam için önceki satırı da al if i1 == 0: return None i1 -= 1 j1 -= 1 frm, to = "\n".join(a[i1:i2]), "\n".join(b[j1:j2]) k = i1 while correct.count(frm) != 1 and k > 0: # benzersiz olana kadar yukarı doğru bağlam ekle k -= 1 frm, to = a[k] + "\n" + frm, a[k] + "\n" + to return (frm, to) if correct.count(frm) == 1 and frm != to else None def python_item(qb: Path, name: str): buggy = (qb / "python_programs" / f"{name}.py").read_text() correct = (qb / "correct_python_programs" / f"{name}.py").read_text() _, doc = docstring(buggy) code_correct, _ = docstring(correct) code = code_correct.rstrip() + ('\n\n\n"""\n' + doc + '\n"""\n' if doc else "\n") buggy_code, _ = docstring(buggy) patch = one_hunk(code_correct, buggy_code) tpath = qb / "python_testcases" / f"test_{name}.py" jpath = qb / "json_testcases" / f"{name}.json" if not patch or not doc or not tpath.exists() or not jpath.exists(): return None t = tpath.read_text() if "load_json_testcases" not in t: return None cases = [json.loads(ln) for ln in jpath.read_text().splitlines() if ln.strip()] t = t.replace("from load_testdata import load_json_testcases\n", "") t = re.sub(r"if pytest\.use_correct:\n\s+from correct_python_programs\.\w+ import (\w+)\nelse:\n" r"\s+from python_programs\.\w+ import \w+\n", r"from solution import \1\n", t) t = re.sub(r"load_json_testcases\([\w.]+\)", repr(cases), t) t = t.replace("pytest.run_slow", "False") if "python_programs" in t or "use_correct" in t: return None return {"code": code, "tests": t, "patch": patch, "ext": "py", "file": f"{name}.py"} def java_item(qb: Path, name: str, doc: str): up = name.upper() cpath, bpath = qb / "correct_java_programs" / f"{up}.java", qb / "java_programs" / f"{up}.java" tpath = qb / "java_testcases" / "junit" / f"{up}_TEST.java" if not (cpath.exists() and bpath.exists() and tpath.exists()): return None strip_pkg = lambda s: re.sub(r"^\s*package\s+[\w.]+;\s*\n", "", s, flags=re.M) # noqa: E731 strip_tpl = lambda s: re.sub(r"/\*\s*\n \* To change this template.*?\*/\s*\n", "", s, flags=re.S) # noqa: E731 correct, buggy = strip_tpl(strip_pkg(cpath.read_text())), strip_tpl(strip_pkg(bpath.read_text())) patch = one_hunk(correct, buggy) t = tpath.read_text() if not patch or re.search(r"\b(Node|WeightedEdge)\b", correct + t): return None # özgün dosyadaki yalnızca @author içeren Javadoc bloğu, açıklamayla tek blokta birleştirilir am = re.search(r"/\*\*\s*\n\s*\*\s*\n\s*\*\s*@author\s+(\S+)\s*\n\s*\*/\s*\n", correct) author = am.group(1) if am else "" if am: correct = correct[:am.start()] + correct[am.end():] javadoc = ("/**\n * " + "\n * ".join(doc.splitlines()) + ("\n *\n * @author " + author if author else "") + "\n */\n") if doc else "" code = re.sub(r"(public class " + up + r"\b)", javadoc.replace("\\", "\\\\") + r"\1", correct, count=1) t = strip_pkg(t).replace("java_programs.", "").replace(f"class {up}_TEST", f"class {up}Test") if "QuixFixOracleHelper" in t: helper = strip_pkg((qb / "java_testcases" / "junit" / "QuixFixOracleHelper.java").read_text()) helper = helper.replace("public class QuixFixOracleHelper", "class QuixFixOracleHelper") t = t.replace("java_testcases.junit.QuixFixOracleHelper", "QuixFixOracleHelper") + "\n" + helper t5 = (t.replace("@org.junit.Test(timeout = 3000)", "@org.junit.jupiter.api.Test @org.junit.jupiter.api.Timeout(value = 3000, unit = " "java.util.concurrent.TimeUnit.MILLISECONDS)") .replace("org.junit.Assert.", "org.junit.jupiter.api.Assertions.")) return {"code": code, "tests": t, "tests5": t5, "patch": patch, "ext": "java", "file": f"{up}.java"} def validate(lang: str, it: dict) -> tuple[bool, str]: runner = get_runner(lang) junit4 = lang == "java" and runner.framework() == "junit4" tests = it["tests"] if (lang != "java" or junit4) else it["tests5"] base = runner.run(it["code"], tests, "unit", None, with_coverage=False, timeout=60) if not base.all_green: return False, f"referans yeşil değil ({base.passed}/{base.total}, derlendi={base.compiled})" buggy = it["code"].replace(it["patch"][0], it["patch"][1], 1) r = runner.run(buggy, tests, "unit", None, with_coverage=False, timeout=60) caught = (not r.compiled) or r.timed_out or bool(r.failing_names()) return (True, "ok") if caught else (False, "özgün hata referans testlerce yakalanmıyor") def main() -> None: ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("quixbugs", type=Path) ap.add_argument("--per-language", type=int, default=10) ap.add_argument("--seed", type=int, default=2026) args = ap.parse_args() qb = args.quixbugs names = sorted(p.stem for p in (qb / "correct_python_programs").glob("*.py") if not p.stem.endswith("_test") and p.stem != "node") py_ok, java_ok, report = {}, {}, [] for n in names: p = python_item(qb, n) if p: ok, why = validate("python", p) report.append(("python", n, why)) if ok: py_ok[n] = p doc = docstring((qb / "python_programs" / f"{n}.py").read_text())[1] j = java_item(qb, n, doc) if j: ok, why = validate("java", j) report.append(("java", n, why)) if ok: java_ok[n] = j print(f" {n:<28} python={'✓' if n in py_ok else '·'} java={'✓' if n in java_ok else '·'}", flush=True) # Farklı programları iki dile paylaştır (aynı algoritma iki kez sayılmasın) rng = random.Random(args.seed) only_java = [n for n in java_ok if n not in py_ok] both = [n for n in java_ok if n in py_ok] rng.shuffle(only_java) rng.shuffle(both) java_sel = (only_java + both)[:args.per_language] py_pool = [n for n in py_ok if n not in java_sel] rng.shuffle(py_pool) py_sel = py_pool[:args.per_language] if OUT.exists(): shutil.rmtree(OUT) for d in ("python", "java", "reference"): (OUT / d).mkdir(parents=True) lic = (qb / "LICENSE").read_text() (OUT / "LICENSE-QuixBugs.txt").write_text(lic) (OUT / "README.md").write_text( "# QuixBugs alt kümesi (bağımsız bölüm)\n\nKaynak: https://github.com/jkoppel/QuixBugs (MIT, © 2017-2019 " "James Koppel). Programlar ve hatalar QuixBugs yazarlarına aittir. Dönüşümler: doğru sürüm test edilecek " "kod olarak alındı; Python açıklama dizgisi Java sürümüne Javadoc olarak eklendi; QuixBugs test vakaları " "pytest / JUnit biçimine çevrildi. Üretim: `scripts/import_quixbugs.py`.\n") manifest_path = ROOT / "benchmarks" / "manifest.json" manifest = [m for m in json.loads(manifest_path.read_text(encoding="utf-8")) if m.get("split") != "bagimsiz"] for lang, sel, pool in (("python", py_sel, py_ok), ("java", java_sel, java_ok)): for n in sorted(sel): it = pool[n] (OUT / lang / it["file"]).write_text(it["code"]) short = "py" if lang == "python" else "java" ref = OUT / "reference" / f"qb-{short}-{n}" entry = {"id": f"qb-{short}-{n}", "title": f"QuixBugs: {n} ({'Python' if lang == 'python' else 'Java'})", "language": lang, "kind": "unit", "file": f"quixbugs/{lang}/{it['file']}", "description": "QuixBugs programı; tek satırlık özgün hata", "split": "bagimsiz", "source": SOURCE, "seeded_bugs": [{"description": "QuixBugs özgün hatası", "from": it["patch"][0], "to": it["patch"][1]}]} if lang == "python": ref.with_suffix(".py").write_text(it["tests"]) entry["reference_tests"] = f"quixbugs/reference/{ref.name}.py" else: ref.with_suffix(".junit5.java").write_text(it["tests5"]) ref.with_suffix(".junit4.java").write_text(it["tests"]) entry["reference_tests"] = f"quixbugs/reference/{ref.name}.junit5.java" entry["reference_tests_junit4"] = f"quixbugs/reference/{ref.name}.junit4.java" manifest.append(entry) manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8") (OUT / "import_report.json").write_text(json.dumps( {"seed": args.seed, "python_valid": sorted(py_ok), "java_valid": sorted(java_ok), "python_selected": sorted(py_sel), "java_selected": sorted(java_sel), "checks": [{"language": a, "program": b, "result": c} for a, b, c in report]}, ensure_ascii=False, indent=1)) print(f"\nGeçerli: Python {len(py_ok)}, Java {len(java_ok)}. Seçilen: Python {sorted(py_sel)}; Java {sorted(java_sel)}") if __name__ == "__main__": main()