SevdanurGenc's picture
Upload 300 files
9438a57 verified
Raw History Blame Contribute Delete
9.26 kB
"""Java: javac + JUnit 5 (console launcher) + JaCoCo.
Docker imajında JUnit 5 ve JaCoCo hazırdır. Yerel modda `JAVA_LIB_DIR` içinde
`junit-platform-console-standalone.jar` (+ isteğe bağlı `jacocoagent.jar`, `jacococli.jar`)
aranır; yoksa JUnit 4 (`junit.jar` + `hamcrest-core.jar`) ile kapsama olmadan çalışılır.
"""
from __future__ import annotations
import re
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Optional
from ..config import settings
from ..models import RunResult, TestCaseResult, TestKind
from .base import COMPILE_FAIL_RC, LanguageRunner, fill_counts, make_coverage, parse_junit_xml, truncate
from .executor import ExecResult
PKG_RE = re.compile(r"^\s*package\s+([\w.]+)\s*;", re.M)
CLASS_RE = re.compile(r"^\s*public\s+(?:final\s+|abstract\s+)*(?:class|interface|enum|record)\s+(\w+)", re.M)
ANY_CLASS_RE = re.compile(r"^\s*(?:public\s+)?(?:final\s+|abstract\s+)*(?:class|interface|enum|record)\s+(\w+)", re.M)
def java_package(code: str) -> str:
m = PKG_RE.search(code)
return m.group(1) if m else ""
def java_class(code: str, default: str = "Solution") -> str:
m = CLASS_RE.search(code) or ANY_CLASS_RE.search(code)
return m.group(1) if m else default
class JavaRunner(LanguageRunner):
language = "java"
def image(self) -> str:
return settings.docker_image_java
# --- Framework tespiti ---
def framework(self) -> str:
if self.ex.mode != "local":
return "junit5"
lib = Path(self.ex.java_lib)
if (lib / "junit-platform-console-standalone.jar").exists():
return "junit5"
if (lib / "junit.jar").exists():
return "junit4"
return "missing"
def has_jacoco(self) -> bool:
if self.ex.mode != "local":
return True
lib = Path(self.ex.java_lib)
return (lib / "jacocoagent.jar").exists() and (lib / "jacococli.jar").exists()
@property
def supports_coverage(self) -> bool: # type: ignore[override]
return self.has_jacoco()
def source_filename(self, code: str) -> str:
return f"{java_class(code)}.java"
def test_filename(self, code: str, kind: TestKind) -> str:
suffix = "InterfaceTest" if kind == "interface" else "Test"
return f"{java_class(code)}{suffix}.java"
def framework_hint(self, code: str, kind: TestKind) -> str:
cls, pkg = java_class(code), java_package(code)
test_cls = Path(self.test_filename(code, kind)).stem
pkg_line = f"Put `package {pkg};` at the top of the test (same package as the code). " if pkg else \
"The code is in the default package; do NOT add a package declaration. "
if self.framework() == "junit4":
fw = ("Framework: JUnit 4.13 (`import org.junit.Test; import static org.junit.Assert.*;`). "
"Use `@Test(expected = X.class)` or `assertThrows` for exceptions.")
else:
fw = ("Framework: JUnit 5 Jupiter (`import org.junit.jupiter.api.*; import static "
"org.junit.jupiter.api.Assertions.*;`, `@ParameterizedTest` with `@CsvSource` is available "
"via `org.junit.jupiter.params`). Use `assertThrows` for exceptions and `assertEquals(expected, "
"actual, delta)` for doubles.")
hint = (f"{fw} Java 21. The class under test is `{cls}`. {pkg_line}The test class MUST be named "
f"`{test_cls}` (file `{test_cls}.java`). Only the JDK standard library and JUnit are available; "
"no Mockito.")
if kind == "interface":
hint += (" These are INTERFACE / CONTRACT tests: check the public API via reflection where useful "
"(public methods, their parameter/return types, implemented interfaces), the exceptions "
"declared/thrown by public methods, and end-to-end collaboration between public methods.")
return hint
def extra_path(self, wd: Path, rel: Path, content: str) -> Path:
if rel.suffix != ".java":
return wd / "src" / "main" / "resources" / rel
pkg = java_package(content)
# Java'da public sınıf kendi adını taşıyan dosyada olmalı: Test02.java içindeki "public class InventoryManager"
# InventoryManager.java olarak yazılır (yoksa tüm derleme başarısız olur).
name = f"{java_class(content, rel.stem)}.java"
return wd / "src" / "main" / (Path(*pkg.split(".")) if pkg else Path()) / name
def write_files(self, wd: Path, code: str, test_code: str, kind: TestKind, html: Optional[str]) -> None:
pkg = java_package(code)
rel = Path(*pkg.split(".")) if pkg else Path()
main = wd / "src" / "main" / rel
test = wd / "src" / "test" / rel
main.mkdir(parents=True, exist_ok=True)
test.mkdir(parents=True, exist_ok=True)
(main / self.source_filename(code)).write_text(code, encoding="utf-8")
test_name = f"{java_class(test_code, Path(self.test_filename(code, kind)).stem)}.java"
(test / test_name).write_text(test_code, encoding="utf-8")
def command(self, wd: Path, code: str, test_code: str, kind: TestKind, with_coverage: bool) -> str:
lib = self.ex.java_lib
fw = self.framework()
if fw == "missing":
return (f"echo '__LTA_COMPILE_FAILED__ JUnit bulunamadı: {lib} dizinine "
f"junit-platform-console-standalone.jar koyun (README).'; exit {COMPILE_FAIL_RC}")
test_cp = f"{lib}/junit-platform-console-standalone.jar" if fw == "junit5" else \
f"{lib}/junit.jar:{lib}/hamcrest-core.jar"
compile_ = (
"unset JAVA_TOOL_OPTIONS; mkdir -p .lta out/main out/test && "
"( { javac -nowarn -encoding UTF-8 -d out/main $(find src/main -name '*.java') && "
f"javac -nowarn -encoding UTF-8 -d out/test -cp out/main:{test_cp} $(find src/test -name '*.java'); }} "
f"> .lta/compile.log 2>&1 || {{ echo __LTA_COMPILE_FAILED__; cat .lta/compile.log; exit {COMPILE_FAIL_RC}; }} )"
)
if fw == "junit4":
pkg = java_package(code)
test_cls = java_class(test_code, Path(self.test_filename(code, kind)).stem)
fq = f"{pkg}.{test_cls}" if pkg else test_cls
return f"{compile_} && java -cp out/main:out/test:{test_cp} org.junit.runner.JUnitCore {fq}"
agent = ""
if with_coverage and self.has_jacoco():
agent = f"-javaagent:{lib}/jacocoagent.jar=destfile=.lta/jacoco.exec"
run = (f"java {agent} -jar {lib}/junit-platform-console-standalone.jar execute "
"--class-path out/main:out/test --scan-class-path --disable-banner --disable-ansi-colors "
"--details=none --reports-dir=.lta/reports; rc=$?")
report = ""
if agent:
report = (f"; [ -f .lta/jacoco.exec ] && java -jar {lib}/jacococli.jar report .lta/jacoco.exec "
"--classfiles out/main --sourcefiles src/main --xml .lta/jacoco.xml > /dev/null 2>&1")
return f"{compile_} && {{ {run}{report}; exit $rc; }}"
def parse(self, wd: Path, res: ExecResult, code: str, kind: TestKind) -> RunResult:
if self.framework() == "junit4":
return self._parse_junit4(res)
reports = list((wd / ".lta" / "reports").glob("*.xml"))
cases = parse_junit_xml(reports)
for c in cases: # "pkg.FooTest::addsNumbers()" -> "FooTest.addsNumbers"
cls, _, name = c.name.partition("::")
c.name = f"{cls.split('.')[-1]}.{name.rstrip('()')}" if name else c.name
result = fill_counts(RunResult(cases=cases, output=truncate(res.combined)))
jx = wd / ".lta" / "jacoco.xml"
if jx.exists():
result.coverage = _jacoco_lines(jx, self.source_filename(code))
return result
def _parse_junit4(self, res: ExecResult) -> RunResult:
out = res.combined
cases: list[TestCaseResult] = []
failed_names = re.findall(r"^\d+\) (\w+)\(([\w.$]+)\)", out, re.M)
m_ok = re.search(r"OK \((\d+) tests?\)", out)
m_fail = re.search(r"Tests run: (\d+),\s+Failures: (\d+)", out)
total = int(m_ok.group(1)) if m_ok else int(m_fail.group(1)) if m_fail else 0
for name, cls in failed_names:
block = out.split(f") {name}({cls})", 1)[-1][:800]
cases.append(TestCaseResult(name=f"{cls.split('.')[-1]}.{name}", status="failed", message=block.strip()))
for i in range(total - len(failed_names)):
cases.append(TestCaseResult(name=f"passed_{i + 1}", status="passed"))
return fill_counts(RunResult(cases=cases, output=truncate(out)))
def _jacoco_lines(path: Path, filename: str):
covered: set[int] = set()
missed: set[int] = set()
root = ET.parse(path).getroot()
for sf in root.iter("sourcefile"):
if sf.get("name") != filename:
continue
for ln in sf.iter("line"):
nr, ci, mi = int(ln.get("nr")), int(ln.get("ci", 0)), int(ln.get("mi", 0))
if ci > 0:
covered.add(nr)
elif mi > 0:
missed.add(nr)
return make_coverage(covered, missed)