///|
fn read_uint_after(text : StringView, marker : StringView) -> Int32? {
let start = match text.find(marker) {
Some(index) => index
None => return None
}
let mut index = start + marker.length()
while index < text.length() {
let ch = text.get_char(index).unwrap()
if ch >= '0' && ch <= '9' {
break
}
index += 1
}
let mut value = 0
let mut found = false
while index < text.length() {
let ch = text.get_char(index).unwrap()
if ch >= '0' && ch <= '9' {
value = value * 10 + (ch.to_int() - '0'.to_int())
found = true
index += 1
} else {
break
}
}
if found {
Some(value)
} else {
None
}
}
///|
fn combined_output(process : ProcessResult) -> String {
if process.stderr.is_empty() {
process.stdout
} else if process.stdout.is_empty() {
process.stderr
} else {
"\{process.stdout}\n\{process.stderr}"
}
}
///|
pub fn sanitize_process_result(
process : ProcessResult,
workspace : StringView,
) -> ProcessResult {
ProcessResult::{
exit_code: process.exit_code,
timed_out: process.timed_out,
stdout: process.stdout.replace_all(old=workspace, new=""),
stderr: process.stderr.replace_all(old=workspace, new=""),
duration_ms: process.duration_ms,
}
}
///|
fn output_excerpt(text : StringView, limit : Int32) -> String {
let clean = text.trim()
if clean.length() <= limit {
clean.to_owned()
} else {
"\{clean[:limit].to_owned()}\n..."
}
}
///|
fn diagnostic_hints(
check : CheckSpec,
process : ProcessResult,
output : StringView,
) -> Array[String] {
let hints = []
if process.timed_out {
hints.push(
"检查超时:减少循环次数或死循环,并确认测试能够在时限内完成。",
)
} else if process.exit_code != 0 {
if output.contains("[4014]") || output.contains("[4015]") {
hints.push(
"类型不匹配:对照函数签名检查表达式、返回值和参数类型。",
)
}
if output.contains("[3002]") || output.contains("[3003]") {
hints.push(
"语法错误:检查括号、关键字、逗号和缺失的表达式。",
)
}
if output.contains("[4021]") {
hints.push(
"未定义标识符:检查拼写、导入语句和名称可见性。",
)
}
if output.contains("Total tests:") && output.contains("failed:") {
hints.push(
"测试未全部通过:从第一个失败用例开始修复,不要只看最终汇总。",
)
}
if hints.is_empty() && !check.hint.is_empty() {
hints.push(check.hint)
}
if hints.is_empty() {
hints.push(
"检查未通过:请查看下方输出摘要并重新运行。",
)
}
}
hints
}
///|
fn test_progress(output : StringView) -> (Int32, Int32)? {
let total = read_uint_after(output, "Total tests:")
let passed = read_uint_after(output, "passed:")
match (total, passed) {
(Some(total), Some(passed)) if total > 0 && passed <= total =>
Some((passed, total))
_ => None
}
}
///|
pub fn score_check(check : CheckSpec, process : ProcessResult) -> CheckResult {
let output = combined_output(process)
let (earned_points, passed) = if process.timed_out {
(0, false)
} else if same_kind(check.kind, Test) {
match test_progress(output) {
Some((passed_tests, total_tests)) =>
(
check.points * passed_tests / total_tests,
process.exit_code == 0 && passed_tests == total_tests,
)
None =>
if process.exit_code == 0 {
(check.points, true)
} else {
(0, false)
}
}
} else if process.exit_code == 0 {
(check.points, true)
} else {
(0, false)
}
CheckResult::{
id: check.id,
title: check.title,
passed,
earned_points,
possible_points: check.points,
exit_code: process.exit_code,
timed_out: process.timed_out,
duration_ms: process.duration_ms,
feedback: diagnostic_hints(check, process, output),
output_excerpt: output_excerpt(output, 1200),
}
}
///|
pub fn build_report(
assignment : Assignment,
student_path : StringView,
results : Array[CheckResult],
) -> GradeReport {
let mut score = 0
let mut required_failed = false
let result_map : Map[String, CheckResult] = Map([])
for result in results {
score += result.earned_points
result_map.set(result.id, result)
}
for check in assignment.checks {
if check.required {
match result_map.get(check.id) {
Some(result) if !result.passed => required_failed = true
None => required_failed = true
_ => ()
}
}
}
GradeReport::{
assignment_name: assignment.name,
student_path: student_path.to_owned(),
score,
max_score: assignment.max_score,
passed: !required_failed && score >= assignment.pass_score,
checks: results,
}
}