跳转至

Docker 镜像构建指引

评测过程简介

在学生上传提交自己的代码文件后,Autograder 将启动作业所指定的 Docker 镜像,并将学生提交的文件挂载到 /autograder/submission 目录下。之后,Autograder 将在 /autograder 目录下运行 /autograder/run 文件,这个文件可以是 Bash 脚本,也可以是 Python 脚本,还可以是二进制程序,只要是一个可执行文件即可。如果是脚本文件,需要使用 #! 语法来指定脚本解释器的目录。在 /autograder/run 程序退出后,Autograder 将读取解析 /autograder/results/results.json 文件,并将结果以及程序返回码写回数据库。

评测结果格式

其中,results.json 文件格式以及样例如下:

{
    "tests": [ // 测试结果,由多个测试用例构成
        {
            "name": "测试用例 1", // 测试用例的名字
            "score": 10, // 测试用例的分数
            "max_score": 20, // 测试用例的满分
            "output": "Any output message", // 希望学生看到的输出信息(可选)
            "output_path": "path/to/output/file" // 希望学生下载的输出文件,需要在 /autograder/results/outputs 目录下(可选)
        },
        {
            "name": "测试用例 2", // 测试用例的名字
            "score": 20, // 测试用例的分数
            "max_score": 20, // 测试用例的满分
            "output": "Any output message", // 希望学生看到的输出信息(可选)
            "output_path": "path/to/output/file" // 希望学生下载的输出文件,需要在 /autograder/results/outputs 目录下(可选)
        },
    ],
    "leaderboard": [ // 排行榜信息,将用于排行榜的显示(可选)
        {
            "name": "准确率", // 排名分数项的名字
            "value": 23.33, // 排名分数项的值,可以为数字或字符串,字符串按字典序比较
            "order": 1, // 排名分数项的顺序,为整数,数字越小优先级越高,默认以及最小值为 0,多个顺序重复时,排序结果未定义
            "is_desc": true, // 排名分数项的排序规则,true 为降序,false 为升序,默认为 false
            "suffix": "%", // 排名分数项的单位后缀,为字符串(可选)
        },
        {
            "name": "运行时间",
            "value": 126,
            "order": 2,
            "suffix": "ms"
        }
    ]
    // 排行榜如果为空,则该提交无法获得排行榜排名
    // 对于所有提交而言,如果排行榜不为空,则排行榜分数项的名字、顺序和排序规则应当相同
    // 如果有分数项定义不一致,则排序结果未定义
}

无论测试是否出错,都应该生成这样的报告文件,否则将视为发生了内部错误。

Docker 镜像需要您在本地构建好之后上传至 Docker Hub,并确保 Autograder 有权限访问该镜像。镜像可以指定 tag,以方便复用镜像。如果您的测试需要使用额外的数据,也需要打包在 Docker 镜像中一并上传。Docker 镜像本身有复用机制,通过恰当编写 Dockerfile 文件,即使不同作业使用不同的镜像,也不会带来额外的存储开销。

运行脚本参考样例

注意换行符

如果您在 Windows 平台上编写脚本,需要注意保存的时候要使用 LF 换行符而不是 CRLF 换行符,否则会导致脚本无法运行。

#!/usr/bin/env python3

import json
import subprocess
import shutil

class TestAborted:
    pass

if __name__ == "__main__":
    report = {}
    report["tests"] = []
    report["leaderboard"] = []

    # Copy files from the submission to working directory
    shutil.copy2("/autograder/submission/hello.cpp", "./hello.cpp")
    shutil.copy2("/autograder/submission/world.cpp", "./world.cpp")
    try:
        # Compile
        result = subprocess.run(["g++", "-o", "hello_world", "hello.cpp", "world.cpp"], capture_output=True)
        compile_case = {"name": "编译", "max_score": 20, "score": 0}
        if result.returncode != 0:
            compile_case["output"] = result.stdout
            report["tests"].append(compile_case)
            raise TestAborted
        compile_case["score"] = 20
        report["tests"].append(compile_case)

        # Run
        run_case = {"name": "运行测试", "max_score": 80, "score": 0}
        result = subprocess.run(["./hello_world"], capture_output=True)
        if result.stdout == "Hello, world!":
            run_case["score"] = 80
        else:
            run_case["output"] = result.stdout
        report["tests"].append(run_case)

        # Leaderboard
        statinfo = os.stat("hello.cpp")
        filesize = statinfo.st_size
        leaderboard_item = {"name": "代码大小", "value": filesize, "suffix": "B"}
        report["leaderboard"].append(leaderboard_item)
    except:
        pass

    # Write report
    with open("/autograder/results/results.json", "w") as f:
        json.dump(report, f)

Dockerfile 参考样例

# 指定基础镜像
FROM ubuntu:20.04
# 安装需要的包
RUN apt update && apt install -y g++ python3
WORKDIR /autograder
# 添加评测所需要的文件
ADD run.py /autograder/run

将需要放入的文件和 Dockerfile 保存在一起,参考以下构建指令:

docker build -t howardlau1999/my-assignment:1.0 .

构建完成后,推送到 DockerHub:

docker push howardlau1999/my-assignment:1.0

之后创建作业的时候填入同样的镜像名即可。


最后更新: 2022-03-12 16:08:47
本页作者: Howard Lau