Download scripts/import_quixbugs.py from SevdanurGenc/LLM-Automated-Test-Generation-Agent: direct link, hf CLI and curl.
- Browser
- Download file 11.7 kB
-
https://huggingface.co/SevdanurGenc/LLM-Automated-Test-Generation-Agent/resolve/main/scripts/import_quixbugs.py
- Command line
-
hf download hf://SevdanurGenc/LLM-Automated-Test-Generation-Agent/scripts/import_quixbugs.py
-
curl -L -o import_quixbugs.py https://huggingface.co/SevdanurGenc/LLM-Automated-Test-Generation-Agent/resolve/main/scripts/import_quixbugs.py
11.7 kB
| #!/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() | |