File size: 10,913 Bytes
f52c7b1
 
 
 
 
 
 
1610381
f52c7b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1610381
 
f52c7b1
1610381
 
 
362a3d0
 
1610381
 
 
 
 
 
f52c7b1
 
 
 
 
1610381
f52c7b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1610381
 
 
f52c7b1
 
1610381
 
 
f52c7b1
 
 
 
 
 
 
 
 
 
 
 
362a3d0
f52c7b1
 
 
362a3d0
 
 
 
 
f52c7b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1610381
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
"""Tests for the Docker execution engine (keyless; no Docker daemon needed).

The engine's *selection* logic and *command construction* are tested
here; actually executing a container is verified manually and by the
experiment harness, for the same reason live-LLM calls are excluded
from the unit suite.
"""
import json
from pathlib import Path

import pytest

from autocodabench.runner import execution as ex


# -- engine resolution --------------------------------------------------------

def test_auto_prefers_docker_when_available(monkeypatch):
    monkeypatch.setattr(ex, "_docker_available", lambda: True)
    r = ex.resolve_execution_engine("auto")
    assert r["engine"] == "docker" and r["error"] is None and r["note"] is None


def test_auto_errors_without_docker(monkeypatch):
    # Docker-only: with no daemon there is no fallback — it is a hard error.
    monkeypatch.setattr(ex, "_docker_available", lambda: False)
    r = ex.resolve_execution_engine("auto")
    assert r["engine"] is None
    assert "Docker" in r["error"] and "exclusively through Docker" in r["error"]


def test_conda_engine_removed(monkeypatch):
    # The conda engine has been removed; requesting it returns a clear error.
    monkeypatch.setattr(ex, "_docker_available", lambda: True)
    r = ex.resolve_execution_engine("conda")
    assert r["engine"] is None
    assert "removed" in r["error"]


def test_explicit_docker_errors_without_daemon(monkeypatch):
    monkeypatch.setattr(ex, "_docker_available", lambda: False)
    r = ex.resolve_execution_engine("docker")
    assert r["engine"] is None and "Docker" in r["error"]


def test_unknown_engine_rejected():
    r = ex.resolve_execution_engine("podman")
    assert r["engine"] is None and "unknown engine" in r["error"]


# -- docker command construction ----------------------------------------------

def test_docker_run_mirrors_worker_contract(tmp_path):
    # stage the minimum the mount builder inspects
    (tmp_path / "program" / "scoring_program").mkdir(parents=True)
    (tmp_path / "input").mkdir()
    (tmp_path / "output").mkdir()
    cmd = ex._docker_run(
        "codalab/codalab-legacy:py37", tmp_path, "scoring_program",
        "python3 $program/score.py $input $output",
        {"OMP_NUM_THREADS": "2"}, has_ingestion=False,
    )
    assert cmd.startswith("docker run --rm ")
    # the active program dir is mounted at /app/program, as the worker does
    assert f"-v {tmp_path / 'program' / 'scoring_program'}:/app/program:rw" in cmd
    assert f"-v {tmp_path / 'input'}:/app/input:rw" in cmd
    assert f"-v {tmp_path / 'output'}:/app/output:rw" in cmd
    assert "-w /app/program" in cmd              # worker's working directory
    assert "codalab/codalab-legacy:py37" in cmd
    # canonical $variables resolved to container paths
    assert "/app/program/score.py /app/input /app/output" in cmd
    assert "$program" not in cmd and "$input" not in cmd
    assert "-e PYTHONUNBUFFERED=1" in cmd
    assert "-e OMP_NUM_THREADS=2" in cmd
    # the platform never installs requirements — neither may the engine
    assert "pip install" not in cmd


def test_resolve_command_substitutes_worker_variables(tmp_path):
    # $variables become the worker's absolute container paths; literal /app/...
    # paths are already correct and left untouched.
    out = ex._resolve_command(
        "python3 $program/score.py $input_data $input $output",
        "docker", tmp_path, "scoring_program")
    assert "/app/program/score.py /app/input_data /app/input /app/output" in out
    assert "$" not in out


# -- docker_image resolution from competition.yaml ------------------------------

def test_bundle_docker_image_reads_declared_image(tmp_path):
    bundle = tmp_path / "demo"
    bundle.mkdir()
    (bundle / "competition.yaml").write_text(
        "title: t\ndocker_image: myorg/myimage:1.2\n", encoding="utf-8")
    assert ex.bundle_docker_image("demo", str(tmp_path)) == "myorg/myimage:1.2"


def test_bundle_docker_image_defaults_to_autocodabench_base(tmp_path):
    bundle = tmp_path / "demo"
    bundle.mkdir()
    (bundle / "competition.yaml").write_text("title: t\n", encoding="utf-8")
    # With no declared image, the runner falls back to the autocodabench CPU
    # base image (overridable via AUTOCODABENCH_DOCKER_IMAGE).
    got = ex.bundle_docker_image("demo", str(tmp_path))
    assert got == ex._DEFAULT_DOCKER_IMAGE
    assert "autocodabench-base-cpu" in got


# -- engine plumbing through the sandbox runner ---------------------------------

def test_run_user_submission_requires_daemon_for_explicit_docker(tmp_path, monkeypatch):
    monkeypatch.setattr(ex, "_docker_available", lambda: False)
    bundle = tmp_path / "demo"
    (bundle / "scoring_program").mkdir(parents=True)
    sub = tmp_path / "sub"
    sub.mkdir()
    monkeypatch.setenv("AUTOCODABENCH_BUNDLES_ROOT", str(tmp_path))
    res = ex.run_user_submission("demo", env_name="unused",
                                 submission_dir=str(sub), label="t",
                                 engine="docker")
    assert res["ok"] is False and "Docker daemon" in res["error"]


# -- arch normalization -------------------------------------------------------

@pytest.mark.parametrize("raw,want", [
    ("aarch64", "arm64"), ("arm64", "arm64"), ("arm64/v8", "arm64"),
    ("x86_64", "amd64"), ("amd64", "amd64"),
    ("", "unknown"), (None, "unknown"), ("ppc64le", "ppc64le"),
])
def test_normalize_arch(raw, want):
    assert ex._normalize_arch(raw) == want


# -- image architecture detection (registry/local, no daemon needed) ----------

def _patch_query(monkeypatch, handler):
    """Replace `_docker_query(args, timeout=...)` with a canned dispatcher."""
    monkeypatch.setattr(ex, "_docker_query", lambda args, timeout=20: handler(args))


def test_image_arch_status_local_inspect(monkeypatch):
    def handler(args):
        if args[:2] == ["image", "inspect"]:
            return "arm64", None
        return None, "should not reach the registry for a local image"
    _patch_query(monkeypatch, handler)
    s = ex.image_arch_status("local/img:1")
    assert s["present_locally"] is True
    assert s["arch"] == "arm64" and s["available_arches"] == ["arm64"]
    assert s["multi_arch"] is False and s["source"] == "local image"


def test_image_arch_status_remote_multiarch(monkeypatch):
    # `docker manifest inspect --verbose` returns a JSON array for a manifest
    # list; attestation entries (architecture == "unknown") are dropped.
    manifest = json.dumps([
        {"Descriptor": {"platform": {"architecture": "amd64", "os": "linux"}}},
        {"Descriptor": {"platform": {"architecture": "arm64", "os": "linux"}}},
        {"Descriptor": {"platform": {"architecture": "unknown", "os": "unknown"}}},
    ])
    def handler(args):
        if args[:2] == ["image", "inspect"]:
            return None, "No such image"
        return manifest, None
    _patch_query(monkeypatch, handler)
    s = ex.image_arch_status("codalab/codalab-legacy:py312")
    assert s["present_locally"] is False
    assert s["available_arches"] == ["amd64", "arm64"]
    assert s["multi_arch"] is True and s["arch"] is None
    assert s["source"] == "remote manifest"


def test_image_arch_status_remote_singlearch(monkeypatch):
    # A single-arch image: `--verbose` returns a JSON object, not a list.
    manifest = json.dumps(
        {"Descriptor": {"platform": {"architecture": "amd64", "os": "linux"}}})
    def handler(args):
        if args[:2] == ["image", "inspect"]:
            return None, "No such image"
        return manifest, None
    _patch_query(monkeypatch, handler)
    s = ex.image_arch_status("codalab/codalab-legacy:py39")
    assert s["available_arches"] == ["amd64"] and s["arch"] == "amd64"
    assert s["multi_arch"] is False


def test_image_arch_status_unavailable_tidies_error(monkeypatch):
    # Not local and not in any registry: multi-line auth errors collapse to one.
    def handler(args):
        if args[:2] == ["image", "inspect"]:
            return None, "No such image"
        return None, "errors:\n denied: requested access\n unauthorized: auth required"
    _patch_query(monkeypatch, handler)
    s = ex.image_arch_status("autocodabench/autocodabench-base-cpu:latest")
    assert s["arch"] is None and s["available_arches"] == []
    assert "\n" not in s["error"] and "denied" in s["error"]


# -- docker_preflight: native vs emulated fit ---------------------------------

def _patch_preflight(monkeypatch, *, host, daemon_arch, img):
    monkeypatch.setattr(ex, "_host_arch", lambda: host)
    monkeypatch.setattr(ex, "docker_daemon_status", lambda: {
        "cli_installed": True, "daemon_running": True,
        "os": "linux", "arch": daemon_arch, "server_version": "27.0"})
    monkeypatch.setattr(ex, "image_arch_status", lambda image: img)


def test_preflight_native_when_host_arch_available(monkeypatch):
    _patch_preflight(monkeypatch, host="arm64", daemon_arch="arm64", img={
        "present_locally": True, "arch": "arm64", "available_arches": ["arm64"],
        "multi_arch": False, "source": "local image", "error": None})
    p = ex.docker_preflight("local/img:1")
    assert p["ready"] is True
    assert p["runs_natively"] is True and p["emulated"] is False


def test_preflight_emulated_when_arch_mismatch(monkeypatch):
    _patch_preflight(monkeypatch, host="arm64", daemon_arch="arm64", img={
        "present_locally": False, "arch": "amd64", "available_arches": ["amd64"],
        "multi_arch": False, "source": "remote manifest", "error": None})
    p = ex.docker_preflight("codalab/codalab-legacy:py39")
    assert p["runs_natively"] is False and p["emulated"] is True


def test_preflight_multiarch_runs_natively(monkeypatch):
    _patch_preflight(monkeypatch, host="arm64", daemon_arch="arm64", img={
        "present_locally": False, "arch": None,
        "available_arches": ["amd64", "arm64"], "multi_arch": True,
        "source": "remote manifest", "error": None})
    p = ex.docker_preflight("codalab/codalab-legacy:py312")
    assert p["runs_natively"] is True and p["emulated"] is False


def test_preflight_unknown_when_arch_undetermined(monkeypatch):
    _patch_preflight(monkeypatch, host="arm64", daemon_arch="arm64", img={
        "present_locally": False, "arch": None, "available_arches": [],
        "multi_arch": False, "source": None, "error": "image not available"})
    p = ex.docker_preflight("private/img:1")
    assert p["runs_natively"] is None and p["emulated"] is None


def test_preflight_not_ready_without_daemon(monkeypatch):
    monkeypatch.setattr(ex, "_host_arch", lambda: "arm64")
    monkeypatch.setattr(ex, "docker_daemon_status", lambda: {
        "cli_installed": True, "daemon_running": False,
        "os": None, "arch": None, "server_version": None})
    p = ex.docker_preflight("any/img:1")
    assert p["ready"] is False and p["runs_natively"] is None