SevdanurGenc's picture
Upload 300 files
9438a57 verified
Raw History Blame Contribute Delete
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()